@mirage-engine/core 0.3.21 → 0.3.22
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/CHANGELOG.md +12 -0
- package/dist/mirage-engine.js +1405 -1196
- package/dist/mirage-engine.umd.js +18 -18
- package/dist/src/animation/Animator.d.ts +1 -1
- package/dist/src/core/Engine.d.ts +1 -1
- package/dist/src/core/Syncer.d.ts +3 -1
- package/dist/src/dom/Extractor.d.ts +6 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/renderer/Renderer.d.ts +16 -1
- package/dist/src/types/common.d.ts +7 -0
- package/dist/src/wasm/WasmSynchronizer.d.ts +12 -0
- package/package.json +4 -3
- package/src/animation/Animator.ts +96 -3
- package/src/core/Engine.ts +2 -2
- package/src/core/Syncer.ts +33 -5
- package/src/dom/Extractor.ts +59 -12
- package/src/index.ts +1 -0
- package/src/renderer/Renderer.ts +109 -163
- package/src/types/common.ts +9 -0
- package/src/wasm/WasmSynchronizer.ts +44 -0
|
@@ -2,25 +2,28 @@ import { StyleData } from "@mirage-engine/dom-tracker";
|
|
|
2
2
|
import { MeshRegistry } from "../store/MeshRegistry";
|
|
3
3
|
import { Painter } from "@mirage-engine/painter";
|
|
4
4
|
import * as THREE from "three";
|
|
5
|
+
import { WASM_STRIDE, OFFSET_LOCAL_X, OFFSET_LOCAL_Y, OFFSET_PARENT, OFFSET_WORLD_X, OFFSET_WORLD_Y } from "../types";
|
|
5
6
|
|
|
6
7
|
export function animateMeshByData(
|
|
7
8
|
registry: MeshRegistry,
|
|
8
9
|
data: Map<HTMLElement, StyleData>,
|
|
10
|
+
sharedArray?: Float32Array,
|
|
9
11
|
) {
|
|
10
12
|
if (data.size === 0) return;
|
|
11
13
|
|
|
14
|
+
const elementsToUpdateLayout = new Set<HTMLElement>();
|
|
15
|
+
|
|
12
16
|
data.forEach((styleData, element) => {
|
|
13
17
|
const mesh = registry.get(element);
|
|
14
18
|
if (!mesh || !mesh.userData.basePosition) return;
|
|
15
19
|
|
|
20
|
+
// Fast-path styling updates (colors, opacity, etc.)
|
|
16
21
|
Painter.forceUpdateUniforms(mesh.material as THREE.ShaderMaterial, {
|
|
17
22
|
backgroundColor: styleData.backgroundColor,
|
|
18
23
|
backgroundImage: styleData.backgroundImage,
|
|
19
24
|
boxShadow: styleData.boxShadow,
|
|
20
25
|
opacity: styleData.opacity,
|
|
21
|
-
borderRadius:
|
|
22
|
-
styleData.borderRadius ?? mesh.userData.baseStyles?.borderRadius,
|
|
23
|
-
// width and height are no longer updated here, they are updated in syncMeshesByDOM
|
|
26
|
+
borderRadius: styleData.borderRadius ?? mesh.userData.baseStyles?.borderRadius,
|
|
24
27
|
});
|
|
25
28
|
|
|
26
29
|
if (mesh.userData.nativeMesh) {
|
|
@@ -31,6 +34,96 @@ export function animateMeshByData(
|
|
|
31
34
|
opacity: styleData.opacity,
|
|
32
35
|
borderRadius: styleData.borderRadius ?? mesh.userData.baseStyles?.borderRadius,
|
|
33
36
|
});
|
|
37
|
+
|
|
38
|
+
if (styleData.scaleX !== undefined || styleData.scaleY !== undefined) {
|
|
39
|
+
const nativeMesh = mesh.userData.nativeMesh as THREE.Mesh;
|
|
40
|
+
const currentScaleX = nativeMesh.scale.x;
|
|
41
|
+
const currentScaleY = nativeMesh.scale.y;
|
|
42
|
+
const currentScaleZ = nativeMesh.scale.z;
|
|
43
|
+
nativeMesh.scale.set(
|
|
44
|
+
styleData.scaleX !== undefined ? styleData.scaleX * currentScaleX : currentScaleX,
|
|
45
|
+
styleData.scaleY !== undefined ? styleData.scaleY * currentScaleY : currentScaleY,
|
|
46
|
+
styleData.scaleZ !== undefined ? styleData.scaleZ * currentScaleZ : currentScaleZ
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (styleData.layoutChanged || styleData.width !== undefined || styleData.height !== undefined) {
|
|
52
|
+
elementsToUpdateLayout.add(element);
|
|
53
|
+
// Add all descendants that are in the registry
|
|
54
|
+
const descendants = element.querySelectorAll('*');
|
|
55
|
+
for (let i = 0; i < descendants.length; i++) {
|
|
56
|
+
const desc = descendants[i] as HTMLElement;
|
|
57
|
+
if (registry.has(desc)) {
|
|
58
|
+
elementsToUpdateLayout.add(desc);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} else if (styleData.x !== undefined || styleData.y !== undefined) {
|
|
62
|
+
// Just transform update (x, y) - doesn't affect child layout natively!
|
|
63
|
+
if (sharedArray && mesh.userData.wasmIndex !== undefined) {
|
|
64
|
+
const offset = mesh.userData.wasmIndex * WASM_STRIDE;
|
|
65
|
+
if (styleData.x !== undefined && mesh.userData.initialLocalX !== undefined) {
|
|
66
|
+
sharedArray[offset + OFFSET_LOCAL_X] = mesh.userData.initialLocalX + styleData.x;
|
|
67
|
+
}
|
|
68
|
+
if (styleData.y !== undefined && mesh.userData.initialLocalY !== undefined) {
|
|
69
|
+
sharedArray[offset + OFFSET_LOCAL_Y] = mesh.userData.initialLocalY + styleData.y;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Now, process all layout updates efficiently!
|
|
76
|
+
elementsToUpdateLayout.forEach((element) => {
|
|
77
|
+
const mesh = registry.get(element);
|
|
78
|
+
if (!mesh) return;
|
|
79
|
+
|
|
80
|
+
const rect = element.getBoundingClientRect();
|
|
81
|
+
|
|
82
|
+
if (mesh.userData.domRect) {
|
|
83
|
+
mesh.userData.domRect.width = rect.width;
|
|
84
|
+
mesh.userData.domRect.height = rect.height;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let pad = (mesh.material as THREE.ShaderMaterial).userData?.shadowPadding || 0;
|
|
88
|
+
mesh.scale.set(rect.width + pad * 2, rect.height + pad * 2, 1);
|
|
89
|
+
|
|
90
|
+
Painter.forceUpdateUniforms(mesh.material as THREE.ShaderMaterial, {
|
|
91
|
+
width: rect.width,
|
|
92
|
+
height: rect.height,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
if (sharedArray && mesh.userData.wasmIndex !== undefined) {
|
|
96
|
+
const offset = mesh.userData.wasmIndex * WASM_STRIDE;
|
|
97
|
+
const myWorldX = rect.left + (mesh.userData.isFixed ? 0 : window.scrollX);
|
|
98
|
+
const myWorldY = rect.top + (mesh.userData.isFixed ? 0 : window.scrollY);
|
|
99
|
+
|
|
100
|
+
const parentIndex = sharedArray[offset + OFFSET_PARENT];
|
|
101
|
+
let parentWorldX = 0;
|
|
102
|
+
let parentWorldY = 0;
|
|
103
|
+
if (parentIndex !== -1) {
|
|
104
|
+
const parentOffset = parentIndex * WASM_STRIDE;
|
|
105
|
+
parentWorldX = sharedArray[parentOffset + OFFSET_WORLD_X];
|
|
106
|
+
parentWorldY = sharedArray[parentOffset + OFFSET_WORLD_Y];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
sharedArray[offset + OFFSET_LOCAL_X] = myWorldX - parentWorldX;
|
|
110
|
+
sharedArray[offset + OFFSET_LOCAL_Y] = myWorldY - parentWorldY;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (mesh.userData.nativeMesh) {
|
|
114
|
+
const nativeMesh = mesh.userData.nativeMesh as THREE.Mesh;
|
|
115
|
+
let nPad = (nativeMesh.material as THREE.ShaderMaterial).userData?.shadowPadding || 0;
|
|
116
|
+
nativeMesh.scale.set(rect.width + nPad * 2, rect.height + nPad * 2, 1);
|
|
117
|
+
|
|
118
|
+
Painter.forceUpdateUniforms(nativeMesh.material as THREE.ShaderMaterial, {
|
|
119
|
+
width: rect.width,
|
|
120
|
+
height: rect.height,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (mesh.userData.nativeRect) {
|
|
124
|
+
mesh.userData.nativeRect.width = rect.width;
|
|
125
|
+
mesh.userData.nativeRect.height = rect.height;
|
|
126
|
+
}
|
|
34
127
|
}
|
|
35
128
|
});
|
|
36
129
|
}
|
package/src/core/Engine.ts
CHANGED
package/src/core/Syncer.ts
CHANGED
|
@@ -2,15 +2,18 @@ import { CoreConfig } from "../types/config";
|
|
|
2
2
|
import { Renderer } from "../renderer/Renderer";
|
|
3
3
|
import { MeshRegistry } from "../store/MeshRegistry";
|
|
4
4
|
import { extractSceneGraph } from "../dom/Extractor";
|
|
5
|
-
import { Visibility, USER_LAYER, ATTR_TRAVEL } from "../types";
|
|
5
|
+
import { Visibility, USER_LAYER, ATTR_TRAVEL, WASM_STRIDE } from "../types";
|
|
6
6
|
import { animateMeshByData } from "../animation/Animator";
|
|
7
7
|
import { Tracker } from "@mirage-engine/dom-tracker";
|
|
8
|
+
import { WasmSynchronizer } from "../wasm/WasmSynchronizer";
|
|
8
9
|
|
|
9
10
|
export class Syncer {
|
|
10
11
|
private target: HTMLElement;
|
|
11
12
|
private renderer: Renderer;
|
|
12
13
|
private registry: MeshRegistry;
|
|
13
14
|
private isTravelEnabled: boolean = false;
|
|
15
|
+
private wasmSync: WasmSynchronizer;
|
|
16
|
+
private lastWasmNodeCount: number = 0;
|
|
14
17
|
|
|
15
18
|
public tracker: Tracker;
|
|
16
19
|
|
|
@@ -23,6 +26,7 @@ export class Syncer {
|
|
|
23
26
|
this.target = target;
|
|
24
27
|
this.renderer = renderer;
|
|
25
28
|
this.registry = registry;
|
|
29
|
+
this.wasmSync = new WasmSynchronizer();
|
|
26
30
|
|
|
27
31
|
// Use Tracker from dom-tracker
|
|
28
32
|
this.tracker = new Tracker(target, {
|
|
@@ -38,31 +42,55 @@ export class Syncer {
|
|
|
38
42
|
this.renderer.createRenderTarget();
|
|
39
43
|
}
|
|
40
44
|
|
|
45
|
+
const sharedArray = this.wasmSync.sharedArray;
|
|
46
|
+
this.renderer.updateScroll();
|
|
47
|
+
const extractContext = {
|
|
48
|
+
sharedArray: sharedArray || new Float32Array(),
|
|
49
|
+
currentIndex: 0,
|
|
50
|
+
scrollX: (this.renderer as any).getScrollX(),
|
|
51
|
+
scrollY: (this.renderer as any).getScrollY(),
|
|
52
|
+
};
|
|
53
|
+
|
|
41
54
|
const sceneGraph = extractSceneGraph(
|
|
42
55
|
this.target,
|
|
43
56
|
pendingMask,
|
|
44
57
|
USER_LAYER as Visibility,
|
|
45
58
|
1,
|
|
46
59
|
0,
|
|
47
|
-
this.renderer.qualityFactor
|
|
60
|
+
this.renderer.qualityFactor,
|
|
61
|
+
undefined,
|
|
62
|
+
undefined,
|
|
63
|
+
undefined,
|
|
64
|
+
extractContext
|
|
48
65
|
);
|
|
49
66
|
|
|
50
67
|
if (sceneGraph) {
|
|
68
|
+
this.lastWasmNodeCount = extractContext.currentIndex;
|
|
51
69
|
this.renderer.syncScene(sceneGraph, pendingDeletions);
|
|
70
|
+
|
|
71
|
+
if (sharedArray) {
|
|
72
|
+
this.renderer.saveInitialLocals(sharedArray);
|
|
73
|
+
}
|
|
52
74
|
}
|
|
53
75
|
});
|
|
54
76
|
|
|
55
77
|
this.tracker.onStyleChange.add((pendingStyles) => {
|
|
56
|
-
animateMeshByData(this.registry, pendingStyles);
|
|
78
|
+
animateMeshByData(this.registry, pendingStyles, this.wasmSync.sharedArray || undefined);
|
|
57
79
|
});
|
|
58
80
|
|
|
59
81
|
this.tracker.onRender.add(() => {
|
|
60
|
-
this.renderer.
|
|
82
|
+
this.renderer.updateScroll();
|
|
83
|
+
this.wasmSync.updatePhysics(this.lastWasmNodeCount);
|
|
84
|
+
if (this.wasmSync.sharedArray) {
|
|
85
|
+
this.renderer.syncMeshesByWasm(this.wasmSync.sharedArray);
|
|
86
|
+
}
|
|
61
87
|
this.renderer.render();
|
|
62
88
|
});
|
|
63
89
|
}
|
|
64
90
|
|
|
65
|
-
public start() {
|
|
91
|
+
public async start() {
|
|
92
|
+
// 10000개 노드 분량의 공유 메모리를 넉넉하게 선행 할당 (1-Pass 위함)
|
|
93
|
+
await this.wasmSync.initialize(10000 * WASM_STRIDE);
|
|
66
94
|
this.tracker.start();
|
|
67
95
|
}
|
|
68
96
|
|
package/src/dom/Extractor.ts
CHANGED
|
@@ -14,6 +14,10 @@ import {
|
|
|
14
14
|
ATTR_SELECT,
|
|
15
15
|
ATTR_TRAVEL,
|
|
16
16
|
ATTR_SHADER,
|
|
17
|
+
WASM_STRIDE,
|
|
18
|
+
OFFSET_PARENT,
|
|
19
|
+
OFFSET_LOCAL_X,
|
|
20
|
+
OFFSET_LOCAL_Y,
|
|
17
21
|
} from "../types";
|
|
18
22
|
|
|
19
23
|
import { BoxStyles, TextStyles, ShaderHooks } from "@mirage-engine/painter";
|
|
@@ -210,7 +214,15 @@ export function extractSceneGraph(
|
|
|
210
214
|
inheritedNativeLayer?: number,
|
|
211
215
|
inheritedNativeStyles?: any,
|
|
212
216
|
inheritedClipElements?: HTMLElement[],
|
|
217
|
+
extractContext?: { sharedArray: Float32Array; currentIndex: number; scrollX?: number; scrollY?: number },
|
|
218
|
+
parentWasmIndex: number = -1,
|
|
219
|
+
parentWorldX: number = 0,
|
|
220
|
+
parentWorldY: number = 0,
|
|
221
|
+
inheritedIsFixed: boolean = false
|
|
213
222
|
): SceneNode | null {
|
|
223
|
+
const scrollX = extractContext?.scrollX ?? window.scrollX;
|
|
224
|
+
const scrollY = extractContext?.scrollY ?? window.scrollY;
|
|
225
|
+
|
|
214
226
|
// Check text node
|
|
215
227
|
if (sourceNode.nodeType === Node.TEXT_NODE) {
|
|
216
228
|
const textNode = sourceNode as Text;
|
|
@@ -228,20 +240,34 @@ export function extractSceneGraph(
|
|
|
228
240
|
const computed = parent ? window.getComputedStyle(parent) : null;
|
|
229
241
|
if (!computed) return null;
|
|
230
242
|
|
|
243
|
+
const myIsFixed = inheritedIsFixed || computed.position === "fixed";
|
|
244
|
+
|
|
231
245
|
// Calculate overall bounding box of the lines
|
|
232
246
|
const minX = Math.min(...textLines.map((l) => l.rect.left));
|
|
233
247
|
const minY = Math.min(...textLines.map((l) => l.rect.top));
|
|
234
248
|
const maxX = Math.max(...textLines.map((l) => l.rect.left + l.rect.width));
|
|
235
249
|
const maxY = Math.max(...textLines.map((l) => l.rect.top + l.rect.height));
|
|
236
250
|
|
|
251
|
+
const myWorldX = minX + (myIsFixed ? 0 : scrollX);
|
|
252
|
+
const myWorldY = minY + (myIsFixed ? 0 : scrollY);
|
|
253
|
+
|
|
254
|
+
let myWasmIndex = -1;
|
|
255
|
+
if (extractContext && extractContext.sharedArray) {
|
|
256
|
+
myWasmIndex = extractContext.currentIndex++;
|
|
257
|
+
const offset = myWasmIndex * WASM_STRIDE;
|
|
258
|
+
extractContext.sharedArray[offset + OFFSET_PARENT] = parentWasmIndex;
|
|
259
|
+
extractContext.sharedArray[offset + OFFSET_LOCAL_X] = myWorldX - parentWorldX;
|
|
260
|
+
extractContext.sharedArray[offset + OFFSET_LOCAL_Y] = myWorldY - parentWorldY;
|
|
261
|
+
}
|
|
262
|
+
|
|
237
263
|
// Create SceneNode for the text node
|
|
238
264
|
return {
|
|
239
265
|
id: Math.random().toString(36).substring(2, 9),
|
|
240
266
|
type: "TEXT",
|
|
241
267
|
element: textNode as unknown as HTMLElement,
|
|
242
268
|
rect: {
|
|
243
|
-
x: minX +
|
|
244
|
-
y: minY +
|
|
269
|
+
x: minX + (myIsFixed ? 0 : scrollX),
|
|
270
|
+
y: minY + (myIsFixed ? 0 : scrollY),
|
|
245
271
|
width: maxX - minX,
|
|
246
272
|
height: maxY - minY,
|
|
247
273
|
},
|
|
@@ -264,8 +290,8 @@ export function extractSceneGraph(
|
|
|
264
290
|
textLines: textLines.map((l) => ({
|
|
265
291
|
text: l.text.trim(),
|
|
266
292
|
rect: {
|
|
267
|
-
x: l.rect.left +
|
|
268
|
-
y: l.rect.top +
|
|
293
|
+
x: l.rect.left + (myIsFixed ? 0 : scrollX),
|
|
294
|
+
y: l.rect.top + (myIsFixed ? 0 : scrollY),
|
|
269
295
|
width: l.rect.width,
|
|
270
296
|
height: l.rect.height,
|
|
271
297
|
},
|
|
@@ -275,7 +301,7 @@ export function extractSceneGraph(
|
|
|
275
301
|
visibility: inheritedFlow,
|
|
276
302
|
isTraveler: false,
|
|
277
303
|
captureLayer,
|
|
278
|
-
isFixed:
|
|
304
|
+
isFixed: myIsFixed,
|
|
279
305
|
nativeLayer: inheritedNativeLayer,
|
|
280
306
|
nativeStyles: inheritedNativeStyles
|
|
281
307
|
? {
|
|
@@ -299,13 +325,14 @@ export function extractSceneGraph(
|
|
|
299
325
|
: undefined,
|
|
300
326
|
nativeRect: inheritedNativeStyles
|
|
301
327
|
? {
|
|
302
|
-
x: minX +
|
|
303
|
-
y: minY +
|
|
328
|
+
x: minX + (myIsFixed ? 0 : scrollX),
|
|
329
|
+
y: minY + (myIsFixed ? 0 : scrollY),
|
|
304
330
|
width: maxX - minX,
|
|
305
331
|
height: maxY - minY,
|
|
306
332
|
}
|
|
307
333
|
: undefined,
|
|
308
334
|
clipElements: inheritedClipElements,
|
|
335
|
+
wasmIndex: myWasmIndex !== -1 ? myWasmIndex : undefined,
|
|
309
336
|
children: [],
|
|
310
337
|
};
|
|
311
338
|
}
|
|
@@ -498,6 +525,20 @@ export function extractSceneGraph(
|
|
|
498
525
|
return null;
|
|
499
526
|
}
|
|
500
527
|
|
|
528
|
+
const myIsFixed = inheritedIsFixed || computed.position === "fixed";
|
|
529
|
+
|
|
530
|
+
const myWorldX = rect.left + (myIsFixed ? 0 : scrollX);
|
|
531
|
+
const myWorldY = rect.top + (myIsFixed ? 0 : scrollY);
|
|
532
|
+
|
|
533
|
+
let myWasmIndex = -1;
|
|
534
|
+
if (extractContext && extractContext.sharedArray) {
|
|
535
|
+
myWasmIndex = extractContext.currentIndex++;
|
|
536
|
+
const offset = myWasmIndex * WASM_STRIDE;
|
|
537
|
+
extractContext.sharedArray[offset + OFFSET_PARENT] = parentWasmIndex;
|
|
538
|
+
extractContext.sharedArray[offset + OFFSET_LOCAL_X] = myWorldX - parentWorldX;
|
|
539
|
+
extractContext.sharedArray[offset + OFFSET_LOCAL_Y] = myWorldY - parentWorldY;
|
|
540
|
+
}
|
|
541
|
+
|
|
501
542
|
// [TODO] dataset 방식으로 변경
|
|
502
543
|
let id = element.getAttribute("data-mid");
|
|
503
544
|
if (!id) {
|
|
@@ -627,6 +668,11 @@ export function extractSceneGraph(
|
|
|
627
668
|
? nativeParsedStyles
|
|
628
669
|
: undefined,
|
|
629
670
|
nextClipElements,
|
|
671
|
+
extractContext,
|
|
672
|
+
myWasmIndex,
|
|
673
|
+
myWorldX,
|
|
674
|
+
myWorldY,
|
|
675
|
+
myIsFixed
|
|
630
676
|
);
|
|
631
677
|
if (childNode) {
|
|
632
678
|
children.push(childNode);
|
|
@@ -639,8 +685,8 @@ export function extractSceneGraph(
|
|
|
639
685
|
type: "BOX",
|
|
640
686
|
element,
|
|
641
687
|
rect: {
|
|
642
|
-
x: rect.left +
|
|
643
|
-
y: rect.top +
|
|
688
|
+
x: rect.left + scrollX,
|
|
689
|
+
y: rect.top + scrollY,
|
|
644
690
|
width: rect.width,
|
|
645
691
|
height: rect.height,
|
|
646
692
|
},
|
|
@@ -684,11 +730,11 @@ export function extractSceneGraph(
|
|
|
684
730
|
x:
|
|
685
731
|
nativeParsedStyles.x !== undefined
|
|
686
732
|
? parseFloat(nativeParsedStyles.x)
|
|
687
|
-
: rect.left +
|
|
733
|
+
: rect.left + scrollX,
|
|
688
734
|
y:
|
|
689
735
|
nativeParsedStyles.y !== undefined
|
|
690
736
|
? parseFloat(nativeParsedStyles.y)
|
|
691
|
-
: rect.top +
|
|
737
|
+
: rect.top + scrollY,
|
|
692
738
|
width:
|
|
693
739
|
nativeParsedStyles.width !== undefined
|
|
694
740
|
? parseFloat(nativeParsedStyles.width)
|
|
@@ -699,8 +745,9 @@ export function extractSceneGraph(
|
|
|
699
745
|
: rect.height,
|
|
700
746
|
}
|
|
701
747
|
: undefined,
|
|
702
|
-
isFixed:
|
|
748
|
+
isFixed: myIsFixed,
|
|
703
749
|
clipElements: inheritedClipElements,
|
|
750
|
+
wasmIndex: myWasmIndex !== -1 ? myWasmIndex : undefined,
|
|
704
751
|
children,
|
|
705
752
|
shaderHooks,
|
|
706
753
|
};
|
package/src/index.ts
CHANGED