-
Notifications
You must be signed in to change notification settings - Fork 0
/
element-watcher.js
115 lines (98 loc) · 2.61 KB
/
element-watcher.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/**
* A tile view manages the DOM elements for the entities in a 2D grid.
* Emoji Quest uses tile views for the control pad and inside each 2D facet of
* the 3D world.
*/
// @ts-check
import { assumeDefined } from './lib/assert.js';
import { placeEntity } from './animation2d.js';
/**
* @callback EnterFn
* @param {number} tile
* @param {number} type
*/
/**
* @callback ExitFn
* @param {number} tile
*/
/**
* @template {Element} ChildElement
* @callback ResetFn
* @param {(tile: number, type: number) => ChildElement} createElement
*/
/** @typedef {import('./types.js').PlaceFn} PlaceFn */
/**
* @template {Element} ChildElement
* @typedef {object} ElementWatcher
* @prop {EnterFn} enter
* @prop {ExitFn} exit
* @prop {PlaceFn} place
* @prop {ResetFn<ChildElement>} reset
*/
const noop = () => {};
/**
* @template Element
* @callback AppendChildFn
* @param {Element} child
*/
/**
* @template Element
* @callback RemoveChildFn
* @param {Element} child
*/
/**
* @template Element
* @typedef {Object} ParentElement
* @prop {AppendChildFn<Element>} appendChild
* @prop {RemoveChildFn<Element>} removeChild
*/
/**
* @template {Element} ParentElement
* @template {Element} ChildElement
* @param {ParentElement} $parent
* @param {ChildElement?} $nextSibling
* @param {(tile: number, type: number) => ChildElement} createElement
* @param {(tile: number) => void} [collectElement]
* @return {ElementWatcher<ChildElement>}
*/
export function makeElementWatcher(
$parent,
$nextSibling,
createElement,
collectElement = noop,
) {
const $elements = new Map();
/**
* @param {number} entity
* @param {number} type
*/
function enter(entity, type) {
const $element = createElement(entity, type);
$parent.insertBefore($element, $nextSibling);
$elements.set(entity, $element);
}
/**
* @param {number} entity
*/
function exit(entity) {
const $element = assumeDefined(
$elements.get(entity),
`Assertion failed: cannot remove absent element ${entity}`,
);
$parent.removeChild($element);
collectElement(entity);
}
/** @type {PlaceFn} */
function place(entity, coord, pressure, progress, transition) {
const element = assumeDefined($elements.get(entity));
placeEntity(element, coord, pressure, progress, transition);
}
/** @type {ResetFn<ChildElement>} */
function reset(newCreateElement) {
// TODO If it transpires that the behavior is ever necessary, track all
// current elements that might have invalidated representations and replace
// them.
createElement = newCreateElement;
}
return { enter, exit, place, reset };
}