@flighthq/scene2d-dom 0.2.1-next.840.857425c
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/dist/domBackground.d.ts +3 -0
- package/dist/domBackground.d.ts.map +1 -0
- package/dist/domBackground.js +9 -0
- package/dist/domBackground.js.map +1 -0
- package/dist/domBitmap.d.ts +4 -0
- package/dist/domBitmap.d.ts.map +1 -0
- package/dist/domBitmap.js +81 -0
- package/dist/domBitmap.js.map +1 -0
- package/dist/domCSSFilterBinding.d.ts +19 -0
- package/dist/domCSSFilterBinding.d.ts.map +1 -0
- package/dist/domCSSFilterBinding.js +36 -0
- package/dist/domCSSFilterBinding.js.map +1 -0
- package/dist/domCache.d.ts +14 -0
- package/dist/domCache.d.ts.map +1 -0
- package/dist/domCache.js +64 -0
- package/dist/domCache.js.map +1 -0
- package/dist/domClip.d.ts +3 -0
- package/dist/domClip.d.ts.map +1 -0
- package/dist/domClip.js +38 -0
- package/dist/domClip.js.map +1 -0
- package/dist/domClipContours.d.ts +6 -0
- package/dist/domClipContours.d.ts.map +1 -0
- package/dist/domClipContours.js +48 -0
- package/dist/domClipContours.js.map +1 -0
- package/dist/domClipRectangle.d.ts +6 -0
- package/dist/domClipRectangle.d.ts.map +1 -0
- package/dist/domClipRectangle.js +148 -0
- package/dist/domClipRectangle.js.map +1 -0
- package/dist/domFontSource.d.ts +5 -0
- package/dist/domFontSource.d.ts.map +1 -0
- package/dist/domFontSource.js +18 -0
- package/dist/domFontSource.js.map +1 -0
- package/dist/domHtmlView.d.ts +4 -0
- package/dist/domHtmlView.d.ts.map +1 -0
- package/dist/domHtmlView.js +28 -0
- package/dist/domHtmlView.js.map +1 -0
- package/dist/domImageSource.d.ts +4 -0
- package/dist/domImageSource.d.ts.map +1 -0
- package/dist/domImageSource.js +36 -0
- package/dist/domImageSource.js.map +1 -0
- package/dist/domMaterials.d.ts +15 -0
- package/dist/domMaterials.d.ts.map +1 -0
- package/dist/domMaterials.js +68 -0
- package/dist/domMaterials.js.map +1 -0
- package/dist/domNativeText.d.ts +5 -0
- package/dist/domNativeText.d.ts.map +1 -0
- package/dist/domNativeText.js +88 -0
- package/dist/domNativeText.js.map +1 -0
- package/dist/domNode2D.d.ts +5 -0
- package/dist/domNode2D.d.ts.map +1 -0
- package/dist/domNode2D.js +60 -0
- package/dist/domNode2D.js.map +1 -0
- package/dist/domReconcile.d.ts +9 -0
- package/dist/domReconcile.d.ts.map +1 -0
- package/dist/domReconcile.js +69 -0
- package/dist/domReconcile.js.map +1 -0
- package/dist/domRenderState.d.ts +5 -0
- package/dist/domRenderState.d.ts.map +1 -0
- package/dist/domRenderState.js +43 -0
- package/dist/domRenderState.js.map +1 -0
- package/dist/domRenderView.d.ts +4 -0
- package/dist/domRenderView.d.ts.map +1 -0
- package/dist/domRenderView.js +24 -0
- package/dist/domRenderView.js.map +1 -0
- package/dist/domRichText.d.ts +6 -0
- package/dist/domRichText.d.ts.map +1 -0
- package/dist/domRichText.js +184 -0
- package/dist/domRichText.js.map +1 -0
- package/dist/domScale9Mapper.d.ts +3 -0
- package/dist/domScale9Mapper.d.ts.map +1 -0
- package/dist/domScale9Mapper.js +37 -0
- package/dist/domScale9Mapper.js.map +1 -0
- package/dist/domScale9Shape.d.ts +10 -0
- package/dist/domScale9Shape.d.ts.map +1 -0
- package/dist/domScale9Shape.js +59 -0
- package/dist/domScale9Shape.js.map +1 -0
- package/dist/domShape.d.ts +4 -0
- package/dist/domShape.d.ts.map +1 -0
- package/dist/domShape.js +45 -0
- package/dist/domShape.js.map +1 -0
- package/dist/domStyle.d.ts +9 -0
- package/dist/domStyle.d.ts.map +1 -0
- package/dist/domStyle.js +25 -0
- package/dist/domStyle.js.map +1 -0
- package/dist/domTextHelpers.d.ts +6 -0
- package/dist/domTextHelpers.d.ts.map +1 -0
- package/dist/domTextHelpers.js +8 -0
- package/dist/domTextHelpers.js.map +1 -0
- package/dist/domTextInput.d.ts +4 -0
- package/dist/domTextInput.d.ts.map +1 -0
- package/dist/domTextInput.js +60 -0
- package/dist/domTextInput.js.map +1 -0
- package/dist/domTextLabel.d.ts +4 -0
- package/dist/domTextLabel.d.ts.map +1 -0
- package/dist/domTextLabel.js +75 -0
- package/dist/domTextLabel.js.map +1 -0
- package/dist/domTransform.d.ts +4 -0
- package/dist/domTransform.d.ts.map +1 -0
- package/dist/domTransform.js +16 -0
- package/dist/domTransform.js.map +1 -0
- package/dist/domVideo.d.ts +4 -0
- package/dist/domVideo.d.ts.map +1 -0
- package/dist/domVideo.js +27 -0
- package/dist/domVideo.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
- package/src/domBackground.test.ts +35 -0
- package/src/domBitmap.test.ts +117 -0
- package/src/domCSSFilterBinding.test.ts +62 -0
- package/src/domCache.test.ts +93 -0
- package/src/domClip.test.ts +12 -0
- package/src/domClipContours.test.ts +62 -0
- package/src/domClipRectangle.test.ts +81 -0
- package/src/domFontSource.test.ts +30 -0
- package/src/domHtmlView.test.ts +117 -0
- package/src/domImageSource.test.ts +47 -0
- package/src/domMaterials.test.ts +100 -0
- package/src/domNativeText.test.ts +102 -0
- package/src/domNode2D.test.ts +236 -0
- package/src/domReconcile.test.ts +158 -0
- package/src/domRenderState.test.ts +87 -0
- package/src/domRenderView.test.ts +69 -0
- package/src/domRichText.test.ts +169 -0
- package/src/domScale9Mapper.test.ts +20 -0
- package/src/domScale9Shape.test.ts +76 -0
- package/src/domShape.test.ts +85 -0
- package/src/domStyle.test.ts +130 -0
- package/src/domTextHelpers.test.ts +27 -0
- package/src/domTextInput.test.ts +61 -0
- package/src/domTextLabel.test.ts +92 -0
- package/src/domTransform.test.ts +68 -0
- package/src/domVideo.test.ts +30 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { createRectangle } from '@flighthq/geometry';
|
|
2
|
+
import { addNodeChild, invalidateNodeLocalTransform } from '@flighthq/node';
|
|
3
|
+
import { getOrCreateRenderProxy2D, prepareScene2DRender, registerRenderer } from '@flighthq/render';
|
|
4
|
+
import { createDisplayObject, setNode2DClip } from '@flighthq/scene2d';
|
|
5
|
+
import type { ClipRegion, Rectangle } from '@flighthq/types';
|
|
6
|
+
import { DisplayObjectKind } from '@flighthq/types';
|
|
7
|
+
|
|
8
|
+
import { enableDomClipSupport } from './domClip';
|
|
9
|
+
import { defaultDomScene2DRenderer, drawDomScene2D, renderDomScene2D } from './domNode2D';
|
|
10
|
+
import { createDomRenderState, getDomRenderStateRuntime } from './domRenderState';
|
|
11
|
+
|
|
12
|
+
function makeRectangleClip(rect: Rectangle): ClipRegion {
|
|
13
|
+
return { contours: null, rect, version: 0, winding: 'nonZero' };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function makeState() {
|
|
17
|
+
const container = document.createElement('div');
|
|
18
|
+
return createDomRenderState(container);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Sets up a node with a mock renderer that registers a specific element without going
|
|
22
|
+
// through registerRenderer (which would change rendererMapId and overwrite other nodes).
|
|
23
|
+
function setupRenderedNode(
|
|
24
|
+
state: ReturnType<typeof makeState>,
|
|
25
|
+
obj: ReturnType<typeof createDisplayObject>,
|
|
26
|
+
el: HTMLElement,
|
|
27
|
+
) {
|
|
28
|
+
const data = getOrCreateRenderProxy2D(state, obj);
|
|
29
|
+
data.visible = true;
|
|
30
|
+
data.alpha = 1;
|
|
31
|
+
data.transform2D.a = 1;
|
|
32
|
+
data.transform2D.d = 1;
|
|
33
|
+
data.renderer = {
|
|
34
|
+
createData: vi.fn(),
|
|
35
|
+
submit: vi.fn().mockImplementation(() => {
|
|
36
|
+
getDomRenderStateRuntime(state).domCurrentElement = el;
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
data.rendererMapId = getDomRenderStateRuntime(state).rendererMapId;
|
|
40
|
+
return data;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe('defaultDomScene2DRenderer', () => {
|
|
44
|
+
it('exposes a createData and submit function', () => {
|
|
45
|
+
expect(typeof defaultDomScene2DRenderer.createData).toBe('function');
|
|
46
|
+
expect(typeof defaultDomScene2DRenderer.submit).toBe('function');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('drawDomScene2D', () => {
|
|
51
|
+
it('does not throw for any render proxy', () => {
|
|
52
|
+
const state = makeState();
|
|
53
|
+
const proxy = getOrCreateRenderProxy2D(state, createDisplayObject());
|
|
54
|
+
expect(() => drawDomScene2D(state, proxy)).not.toThrow();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('renderDomScene2D', () => {
|
|
59
|
+
it('does not throw for a simple visible object', () => {
|
|
60
|
+
const state = makeState();
|
|
61
|
+
const obj = createDisplayObject();
|
|
62
|
+
expect(() => renderDomScene2D(state, obj)).not.toThrow();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('removes foreign elements from the container on first render', () => {
|
|
66
|
+
const state = makeState();
|
|
67
|
+
const foreign = document.createElement('span');
|
|
68
|
+
state.element.appendChild(foreign);
|
|
69
|
+
|
|
70
|
+
const obj = createDisplayObject();
|
|
71
|
+
renderDomScene2D(state, obj);
|
|
72
|
+
|
|
73
|
+
expect(state.element.contains(foreign)).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('skips rendering when the object has zero scale', () => {
|
|
77
|
+
const state = makeState();
|
|
78
|
+
const obj = createDisplayObject();
|
|
79
|
+
obj.scaleX = 0;
|
|
80
|
+
obj.scaleY = 0;
|
|
81
|
+
invalidateNodeLocalTransform(obj);
|
|
82
|
+
|
|
83
|
+
const renderer = { createData: vi.fn(), submit: vi.fn() };
|
|
84
|
+
registerRenderer(state, DisplayObjectKind, renderer);
|
|
85
|
+
prepareScene2DRender(state, obj);
|
|
86
|
+
renderDomScene2D(state, obj);
|
|
87
|
+
|
|
88
|
+
expect(renderer.submit).not.toHaveBeenCalled();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('skips rendering invisible objects', () => {
|
|
92
|
+
const state = makeState();
|
|
93
|
+
const obj = createDisplayObject();
|
|
94
|
+
obj.visible = false;
|
|
95
|
+
|
|
96
|
+
const renderer = { createData: vi.fn(), submit: vi.fn() };
|
|
97
|
+
registerRenderer(state, DisplayObjectKind, renderer);
|
|
98
|
+
prepareScene2DRender(state, obj);
|
|
99
|
+
renderDomScene2D(state, obj);
|
|
100
|
+
|
|
101
|
+
expect(renderer.submit).not.toHaveBeenCalled();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('skips objects with zero alpha', () => {
|
|
105
|
+
const state = makeState();
|
|
106
|
+
const obj = createDisplayObject();
|
|
107
|
+
obj.alpha = 0;
|
|
108
|
+
|
|
109
|
+
const renderer = { createData: vi.fn(), submit: vi.fn() };
|
|
110
|
+
registerRenderer(state, DisplayObjectKind, renderer);
|
|
111
|
+
prepareScene2DRender(state, obj);
|
|
112
|
+
renderDomScene2D(state, obj);
|
|
113
|
+
|
|
114
|
+
expect(renderer.submit).not.toHaveBeenCalled();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('calls draw when the object is visible and has a renderer', () => {
|
|
118
|
+
const state = makeState();
|
|
119
|
+
const obj = createDisplayObject();
|
|
120
|
+
const el = document.createElement('div');
|
|
121
|
+
const data = setupRenderedNode(state, obj, el);
|
|
122
|
+
|
|
123
|
+
renderDomScene2D(state, obj);
|
|
124
|
+
|
|
125
|
+
expect(data.renderer!.submit).toHaveBeenCalledOnce();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('traverses children', () => {
|
|
129
|
+
const state = makeState();
|
|
130
|
+
const parent = createDisplayObject();
|
|
131
|
+
const child = createDisplayObject();
|
|
132
|
+
addNodeChild(parent, child);
|
|
133
|
+
|
|
134
|
+
const childEl = document.createElement('div');
|
|
135
|
+
const childData = setupRenderedNode(state, child, childEl);
|
|
136
|
+
|
|
137
|
+
prepareScene2DRender(state, parent);
|
|
138
|
+
renderDomScene2D(state, parent);
|
|
139
|
+
|
|
140
|
+
expect(childData.renderer!.submit).toHaveBeenCalled();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('skips draw on fully static nodes after first render', () => {
|
|
144
|
+
const state = makeState();
|
|
145
|
+
const obj = createDisplayObject();
|
|
146
|
+
const el = document.createElement('div');
|
|
147
|
+
const data = setupRenderedNode(state, obj, el);
|
|
148
|
+
|
|
149
|
+
// First render: node is new, draw must be called.
|
|
150
|
+
renderDomScene2D(state, obj);
|
|
151
|
+
expect(data.renderer!.submit).toHaveBeenCalledTimes(1);
|
|
152
|
+
|
|
153
|
+
// Second render: nothing changed, draw should be skipped.
|
|
154
|
+
renderDomScene2D(state, obj);
|
|
155
|
+
expect(data.renderer!.submit).toHaveBeenCalledTimes(1);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('places rendered elements in scene-graph order', () => {
|
|
159
|
+
const state = makeState();
|
|
160
|
+
const parent = createDisplayObject();
|
|
161
|
+
const childA = createDisplayObject();
|
|
162
|
+
const childB = createDisplayObject();
|
|
163
|
+
addNodeChild(parent, childA);
|
|
164
|
+
addNodeChild(parent, childB);
|
|
165
|
+
|
|
166
|
+
const elA = document.createElement('div');
|
|
167
|
+
const elB = document.createElement('span');
|
|
168
|
+
setupRenderedNode(state, childA, elA);
|
|
169
|
+
setupRenderedNode(state, childB, elB);
|
|
170
|
+
|
|
171
|
+
prepareScene2DRender(state, parent);
|
|
172
|
+
renderDomScene2D(state, parent);
|
|
173
|
+
|
|
174
|
+
const children = Array.from(state.element.children);
|
|
175
|
+
expect(children.indexOf(elA)).toBeLessThan(children.indexOf(elB));
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('reconciles DOM when a child is removed', () => {
|
|
179
|
+
const state = makeState();
|
|
180
|
+
const parent = createDisplayObject();
|
|
181
|
+
const childA = createDisplayObject();
|
|
182
|
+
const childB = createDisplayObject();
|
|
183
|
+
addNodeChild(parent, childA);
|
|
184
|
+
addNodeChild(parent, childB);
|
|
185
|
+
|
|
186
|
+
const elA = document.createElement('div');
|
|
187
|
+
const elB = document.createElement('span');
|
|
188
|
+
setupRenderedNode(state, childA, elA);
|
|
189
|
+
const dataB = setupRenderedNode(state, childB, elB);
|
|
190
|
+
|
|
191
|
+
prepareScene2DRender(state, parent);
|
|
192
|
+
renderDomScene2D(state, parent);
|
|
193
|
+
expect(state.element.children.length).toBe(2);
|
|
194
|
+
|
|
195
|
+
dataB.visible = false;
|
|
196
|
+
|
|
197
|
+
renderDomScene2D(state, parent);
|
|
198
|
+
|
|
199
|
+
expect(state.element.contains(elA)).toBe(true);
|
|
200
|
+
expect(state.element.contains(elB)).toBe(false);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('applies an inherited rectangle clip to child elements', () => {
|
|
204
|
+
const state = makeState();
|
|
205
|
+
enableDomClipSupport(state);
|
|
206
|
+
const parent = createDisplayObject();
|
|
207
|
+
setNode2DClip(parent, makeRectangleClip(createRectangle(10, 20, 30, 40)));
|
|
208
|
+
const child = createDisplayObject();
|
|
209
|
+
addNodeChild(parent, child);
|
|
210
|
+
|
|
211
|
+
prepareScene2DRender(state, parent);
|
|
212
|
+
const el = document.createElement('div');
|
|
213
|
+
setupRenderedNode(state, child, el);
|
|
214
|
+
|
|
215
|
+
renderDomScene2D(state, parent);
|
|
216
|
+
|
|
217
|
+
expect(el.style.clipPath).toBe('polygon(10px 20px, 40px 20px, 40px 60px, 10px 60px)');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('applies a rectangle clip set directly on the clipping node to its content', () => {
|
|
221
|
+
const state = makeState();
|
|
222
|
+
enableDomClipSupport(state);
|
|
223
|
+
const parent = createDisplayObject();
|
|
224
|
+
setNode2DClip(parent, makeRectangleClip(createRectangle(5, 6, 20, 30)));
|
|
225
|
+
const content = createDisplayObject();
|
|
226
|
+
addNodeChild(parent, content);
|
|
227
|
+
|
|
228
|
+
const el = document.createElement('div');
|
|
229
|
+
setupRenderedNode(state, content, el);
|
|
230
|
+
|
|
231
|
+
prepareScene2DRender(state, parent);
|
|
232
|
+
renderDomScene2D(state, parent);
|
|
233
|
+
|
|
234
|
+
expect(el.style.clipPath).toBe('polygon(5px 6px, 25px 6px, 25px 36px, 5px 36px)');
|
|
235
|
+
});
|
|
236
|
+
});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { DomRenderStateRuntime } from '@flighthq/types';
|
|
2
|
+
|
|
3
|
+
import { hasDomStructureChanged, processDomNode, reconcileDomContainer, swapDomOrderLists } from './domReconcile';
|
|
4
|
+
import { createDomRenderState, getDomRenderStateRuntime } from './domRenderState';
|
|
5
|
+
|
|
6
|
+
function makeRuntime(): DomRenderStateRuntime {
|
|
7
|
+
const container = document.createElement('div');
|
|
8
|
+
return getDomRenderStateRuntime(createDomRenderState(container));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe('hasDomStructureChanged', () => {
|
|
12
|
+
it('returns true when needsReconcile is already set', () => {
|
|
13
|
+
const runtime = makeRuntime();
|
|
14
|
+
expect(hasDomStructureChanged(runtime, 0, true)).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('returns true when lengths differ', () => {
|
|
18
|
+
const runtime = makeRuntime();
|
|
19
|
+
runtime.domOrderLength = 2;
|
|
20
|
+
expect(hasDomStructureChanged(runtime, 1, false)).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('returns false when order lists are identical', () => {
|
|
24
|
+
const runtime = makeRuntime();
|
|
25
|
+
const node = {} as any;
|
|
26
|
+
runtime.domOrderLength = 1;
|
|
27
|
+
runtime.domOrderList[0] = node;
|
|
28
|
+
runtime.domNextOrderList[0] = node;
|
|
29
|
+
expect(hasDomStructureChanged(runtime, 1, false)).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('returns true when a node differs at any position', () => {
|
|
33
|
+
const runtime = makeRuntime();
|
|
34
|
+
runtime.domOrderLength = 1;
|
|
35
|
+
runtime.domOrderList[0] = {} as any;
|
|
36
|
+
runtime.domNextOrderList[0] = {} as any;
|
|
37
|
+
expect(hasDomStructureChanged(runtime, 1, false)).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('processDomNode', () => {
|
|
42
|
+
it('calls drawFn and registers the element for a new node', () => {
|
|
43
|
+
const runtime = makeRuntime();
|
|
44
|
+
const node = {} as any;
|
|
45
|
+
const el = document.createElement('div');
|
|
46
|
+
|
|
47
|
+
const result = processDomNode(
|
|
48
|
+
runtime,
|
|
49
|
+
node,
|
|
50
|
+
0,
|
|
51
|
+
() => {
|
|
52
|
+
runtime.domCurrentElement = el;
|
|
53
|
+
},
|
|
54
|
+
0,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
expect(result.newLength).toBe(1);
|
|
58
|
+
expect(result.needsReconcile).toBe(true);
|
|
59
|
+
expect(runtime.domElementMap.get(node)).toBe(el);
|
|
60
|
+
expect(runtime.domNextOrderList[0]).toBe(node);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('skips drawFn for a known node with no dirty frames', () => {
|
|
64
|
+
const runtime = makeRuntime();
|
|
65
|
+
const node = { appearanceFrameId: -1, transformFrameId: -1 } as any;
|
|
66
|
+
const el = document.createElement('div');
|
|
67
|
+
runtime.domElementMap.set(node, el);
|
|
68
|
+
|
|
69
|
+
let called = false;
|
|
70
|
+
const result = processDomNode(
|
|
71
|
+
runtime,
|
|
72
|
+
node,
|
|
73
|
+
0,
|
|
74
|
+
() => {
|
|
75
|
+
called = true;
|
|
76
|
+
},
|
|
77
|
+
0,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
expect(called).toBe(false);
|
|
81
|
+
expect(result.needsReconcile).toBe(false);
|
|
82
|
+
expect(result.newLength).toBe(1);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('sets needsReconcile when the element changes for a known node', () => {
|
|
86
|
+
const runtime = makeRuntime();
|
|
87
|
+
const node = { appearanceFrameId: 1, transformFrameId: -1 } as any;
|
|
88
|
+
const oldEl = document.createElement('div');
|
|
89
|
+
const newEl = document.createElement('canvas');
|
|
90
|
+
runtime.domElementMap.set(node, oldEl);
|
|
91
|
+
|
|
92
|
+
const result = processDomNode(
|
|
93
|
+
runtime,
|
|
94
|
+
node,
|
|
95
|
+
1,
|
|
96
|
+
() => {
|
|
97
|
+
runtime.domCurrentElement = newEl;
|
|
98
|
+
},
|
|
99
|
+
0,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
expect(result.needsReconcile).toBe(true);
|
|
103
|
+
expect(runtime.domElementMap.get(node)).toBe(newEl);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('reconcileDomContainer', () => {
|
|
108
|
+
it('removes foreign elements not in the new element set', () => {
|
|
109
|
+
const runtime = makeRuntime();
|
|
110
|
+
const container = document.createElement('div');
|
|
111
|
+
const foreign = document.createElement('span');
|
|
112
|
+
container.appendChild(foreign);
|
|
113
|
+
|
|
114
|
+
reconcileDomContainer(container, runtime, 0);
|
|
115
|
+
|
|
116
|
+
expect(container.contains(foreign)).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('places tracked elements in correct order', () => {
|
|
120
|
+
const runtime = makeRuntime();
|
|
121
|
+
const container = document.createElement('div');
|
|
122
|
+
const elA = document.createElement('div');
|
|
123
|
+
const elB = document.createElement('span');
|
|
124
|
+
const nodeA = {} as any;
|
|
125
|
+
const nodeB = {} as any;
|
|
126
|
+
runtime.domElementMap.set(nodeA, elA);
|
|
127
|
+
runtime.domElementMap.set(nodeB, elB);
|
|
128
|
+
runtime.domNextOrderList[0] = nodeA;
|
|
129
|
+
runtime.domNextOrderList[1] = nodeB;
|
|
130
|
+
|
|
131
|
+
reconcileDomContainer(container, runtime, 2);
|
|
132
|
+
|
|
133
|
+
const children = Array.from(container.children);
|
|
134
|
+
expect(children.indexOf(elA)).toBeLessThan(children.indexOf(elB));
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('swapDomOrderLists', () => {
|
|
139
|
+
it('promotes the next list to the current list', () => {
|
|
140
|
+
const runtime = makeRuntime();
|
|
141
|
+
const node = {} as any;
|
|
142
|
+
runtime.domNextOrderList[0] = node;
|
|
143
|
+
|
|
144
|
+
swapDomOrderLists(runtime, 1);
|
|
145
|
+
|
|
146
|
+
expect(runtime.domOrderList[0]).toBe(node);
|
|
147
|
+
expect(runtime.domOrderLength).toBe(1);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('recycles the old current list as the next scratch buffer', () => {
|
|
151
|
+
const runtime = makeRuntime();
|
|
152
|
+
const prevList = runtime.domOrderList;
|
|
153
|
+
|
|
154
|
+
swapDomOrderLists(runtime, 0);
|
|
155
|
+
|
|
156
|
+
expect(runtime.domNextOrderList).toBe(prevList);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { EntityRuntimeKey } from '@flighthq/types';
|
|
2
|
+
|
|
3
|
+
import { createDomRenderState, createDomRenderStateRuntime, getDomRenderStateRuntime } from './domRenderState';
|
|
4
|
+
|
|
5
|
+
describe('createDomRenderState', () => {
|
|
6
|
+
it('returns a state with the provided element', () => {
|
|
7
|
+
const div = document.createElement('div');
|
|
8
|
+
const state = createDomRenderState(div);
|
|
9
|
+
expect(state.element).toBe(div);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('sets position:relative and overflow:hidden on the element', () => {
|
|
13
|
+
const div = document.createElement('div');
|
|
14
|
+
createDomRenderState(div);
|
|
15
|
+
expect(div.style.position).toBe('relative');
|
|
16
|
+
expect(div.style.overflow).toBe('hidden');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('defaults roundPixels to false', () => {
|
|
20
|
+
const div = document.createElement('div');
|
|
21
|
+
const state = createDomRenderState(div);
|
|
22
|
+
expect(state.roundPixels).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('sets roundPixels from options', () => {
|
|
26
|
+
const div = document.createElement('div');
|
|
27
|
+
const state = createDomRenderState(div, { roundPixels: true });
|
|
28
|
+
expect(state.roundPixels).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('defaults the runtime currentBlendMode to null', () => {
|
|
32
|
+
const div = document.createElement('div');
|
|
33
|
+
const state = createDomRenderState(div);
|
|
34
|
+
expect(getDomRenderStateRuntime(state).currentBlendMode).toBeNull();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('attaches a populated runtime under EntityRuntimeKey', () => {
|
|
38
|
+
const div = document.createElement('div');
|
|
39
|
+
const state = createDomRenderState(div);
|
|
40
|
+
const runtime = getDomRenderStateRuntime(state);
|
|
41
|
+
expect(runtime.domClipHooks).toBeNull();
|
|
42
|
+
expect(runtime.domCurrentElement).toBeNull();
|
|
43
|
+
expect(runtime.domOrderLength).toBe(-1);
|
|
44
|
+
expect(runtime.domClipStack).toEqual([]);
|
|
45
|
+
expect(runtime.domOrderList).toEqual([]);
|
|
46
|
+
expect(runtime.domNextOrderList).toEqual([]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('defaults backgroundColor to 0', () => {
|
|
50
|
+
const div = document.createElement('div');
|
|
51
|
+
const state = createDomRenderState(div);
|
|
52
|
+
expect(state.backgroundColor).toBe(0);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('sets backgroundColor from options', () => {
|
|
56
|
+
const div = document.createElement('div');
|
|
57
|
+
const state = createDomRenderState(div, { backgroundColor: 0xff0000ff });
|
|
58
|
+
expect(state.backgroundColor).toBe(0xff0000ff);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('sets pixelRatio from options', () => {
|
|
62
|
+
const div = document.createElement('div');
|
|
63
|
+
const state = createDomRenderState(div, { pixelRatio: 3 });
|
|
64
|
+
expect(state.pixelRatio).toBe(3);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('defaults pixelRatio to 1', () => {
|
|
68
|
+
const div = document.createElement('div');
|
|
69
|
+
const state = createDomRenderState(div);
|
|
70
|
+
expect(state.pixelRatio).toBe(1);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('createDomRenderStateRuntime', () => {
|
|
75
|
+
it('allocates an entity runtime with a null binding', () => {
|
|
76
|
+
const runtime = createDomRenderStateRuntime();
|
|
77
|
+
expect(runtime.binding).toBeNull();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('getDomRenderStateRuntime', () => {
|
|
82
|
+
it('returns the runtime attached under EntityRuntimeKey', () => {
|
|
83
|
+
const div = document.createElement('div');
|
|
84
|
+
const state = createDomRenderState(div);
|
|
85
|
+
expect(getDomRenderStateRuntime(state)).toBe(state[EntityRuntimeKey]);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { getOrCreateRenderProxy2D, registerRenderer } from '@flighthq/render';
|
|
2
|
+
import { createRenderView } from '@flighthq/scene2d';
|
|
3
|
+
import type { RenderViewRenderer } from '@flighthq/types';
|
|
4
|
+
import { RenderViewKind } from '@flighthq/types';
|
|
5
|
+
|
|
6
|
+
import { createDomRenderState, getDomRenderStateRuntime } from './domRenderState';
|
|
7
|
+
import { defaultDomRenderViewRenderer, drawDomRenderView } from './domRenderView';
|
|
8
|
+
|
|
9
|
+
function makeState() {
|
|
10
|
+
const container = document.createElement('div');
|
|
11
|
+
const state = createDomRenderState(container);
|
|
12
|
+
registerRenderer(state, RenderViewKind, defaultDomRenderViewRenderer);
|
|
13
|
+
return state;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function makeRenderer(width = 100, height = 100): RenderViewRenderer {
|
|
17
|
+
const canvas = document.createElement('canvas');
|
|
18
|
+
canvas.style.width = `${width}px`;
|
|
19
|
+
canvas.style.height = `${height}px`;
|
|
20
|
+
canvas.width = width;
|
|
21
|
+
canvas.height = height;
|
|
22
|
+
return { canvas, render: () => {} };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getElement(state: ReturnType<typeof makeState>, drawFn: () => void): HTMLElement | null {
|
|
26
|
+
getDomRenderStateRuntime(state).domCurrentElement = null;
|
|
27
|
+
drawFn();
|
|
28
|
+
return getDomRenderStateRuntime(state).domCurrentElement;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('drawDomRenderView', () => {
|
|
32
|
+
it('returns no element when renderer is null', () => {
|
|
33
|
+
const state = makeState();
|
|
34
|
+
const view = createRenderView();
|
|
35
|
+
const renderProxy = getOrCreateRenderProxy2D(state, view);
|
|
36
|
+
const el = getElement(state, () => drawDomRenderView(state, renderProxy));
|
|
37
|
+
expect(el).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('calls render() on the renderer', () => {
|
|
41
|
+
const state = makeState();
|
|
42
|
+
const renderer = makeRenderer();
|
|
43
|
+
const renderSpy = vi.spyOn(renderer, 'render');
|
|
44
|
+
const view = createRenderView({ data: { renderer, width: 100, height: 100 } });
|
|
45
|
+
const renderProxy = getOrCreateRenderProxy2D(state, view);
|
|
46
|
+
drawDomRenderView(state, renderProxy);
|
|
47
|
+
expect(renderSpy).toHaveBeenCalledOnce();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('returns the renderer canvas as the DOM element', () => {
|
|
51
|
+
const state = makeState();
|
|
52
|
+
const renderer = makeRenderer();
|
|
53
|
+
const view = createRenderView({ data: { renderer, width: 100, height: 100 } });
|
|
54
|
+
const renderProxy = getOrCreateRenderProxy2D(state, view);
|
|
55
|
+
const el = getElement(state, () => drawDomRenderView(state, renderProxy));
|
|
56
|
+
expect(el).toBe(renderer.canvas);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('initializes canvas positioning on first draw', () => {
|
|
60
|
+
const state = makeState();
|
|
61
|
+
const renderer = makeRenderer();
|
|
62
|
+
const view = createRenderView({ data: { renderer, width: 100, height: 100 } });
|
|
63
|
+
const renderProxy = getOrCreateRenderProxy2D(state, view);
|
|
64
|
+
drawDomRenderView(state, renderProxy);
|
|
65
|
+
expect(renderer.canvas.style.position).toBe('absolute');
|
|
66
|
+
expect(renderer.canvas.style.transformOrigin).toBe('0 0');
|
|
67
|
+
expect(renderer.canvas.style.pointerEvents).toBe('none');
|
|
68
|
+
});
|
|
69
|
+
});
|