p-elements-core 1.2.30 → 1.2.32-rc-10
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/.editorconfig +17 -17
- package/.gitlab-ci.yml +18 -18
- package/CHANGELOG.md +201 -0
- package/demo/sample.js +1 -1
- package/demo/screen.css +16 -16
- package/demo/theme.css +1 -0
- package/dist/p-elements-core-modern.js +1 -1
- package/dist/p-elements-core.js +1 -1
- package/docs/package-lock.json +6897 -6897
- package/docs/package.json +27 -27
- package/docs/src/404.md +8 -8
- package/docs/src/_data/demos/hello-world/hello-world.tsx +35 -35
- package/docs/src/_data/demos/hello-world/index.html +10 -10
- package/docs/src/_data/demos/hello-world/project.json +7 -7
- package/docs/src/_data/demos/timer/demo-timer.tsx +120 -120
- package/docs/src/_data/demos/timer/icons.tsx +62 -62
- package/docs/src/_data/demos/timer/index.html +12 -12
- package/docs/src/_data/demos/timer/project.json +8 -8
- package/docs/src/_data/global.js +13 -13
- package/docs/src/_data/helpers.js +19 -19
- package/docs/src/_includes/layouts/base.njk +30 -30
- package/docs/src/_includes/layouts/playground.njk +40 -40
- package/docs/src/_includes/partials/app-header.njk +8 -8
- package/docs/src/_includes/partials/head.njk +14 -14
- package/docs/src/_includes/partials/nav.njk +19 -19
- package/docs/src/_includes/partials/top-nav.njk +51 -51
- package/docs/src/documentation/custom-element.md +221 -221
- package/docs/src/documentation/decorators/bind.md +71 -71
- package/docs/src/documentation/decorators/custom-element-config.md +63 -63
- package/docs/src/documentation/decorators/property.md +83 -83
- package/docs/src/documentation/decorators/query.md +66 -66
- package/docs/src/documentation/decorators/render-property-on-set.md +60 -60
- package/docs/src/documentation/decorators.md +9 -9
- package/docs/src/documentation/reactive-properties.md +53 -53
- package/docs/src/index.d.ts +25 -25
- package/docs/src/index.md +3 -3
- package/docs/src/scripts/components/app-mode-switch/app-mode-switch.css +78 -78
- package/docs/src/scripts/components/app-mode-switch/app-mode-switch.tsx +166 -166
- package/docs/src/scripts/components/app-playground/app-playground.tsx +189 -189
- package/docs/tsconfig.json +22 -22
- package/index.html +15 -2
- package/p-elements-core.d.ts +12 -3
- package/package.json +11 -4
- package/readme.md +206 -206
- package/src/custom-element-controller.test.ts +226 -0
- package/src/custom-element-controller.ts +31 -31
- package/src/custom-element.test.ts +906 -0
- package/src/custom-element.ts +471 -188
- package/src/custom-style-element.ts +4 -1
- package/src/decorators/bind.test.ts +163 -0
- package/src/decorators/bind.ts +46 -46
- package/src/decorators/custom-element-config.ts +17 -17
- package/src/decorators/property.test.ts +279 -0
- package/src/decorators/property.ts +822 -150
- package/src/decorators/query.test.ts +146 -0
- package/src/decorators/query.ts +12 -12
- package/src/decorators/render-property-on-set.ts +3 -3
- package/src/helpers/css.test.ts +150 -0
- package/src/helpers/css.ts +71 -71
- package/src/maquette/cache.test.ts +150 -0
- package/src/maquette/cache.ts +35 -35
- package/src/maquette/dom.test.ts +263 -0
- package/src/maquette/dom.ts +115 -115
- package/src/maquette/h.test.ts +165 -0
- package/src/maquette/h.ts +100 -100
- package/src/maquette/index.ts +12 -12
- package/src/maquette/interfaces.ts +536 -536
- package/src/maquette/jsx.ts +61 -61
- package/src/maquette/mapping.test.ts +294 -0
- package/src/maquette/mapping.ts +56 -56
- package/src/maquette/maquette.test.ts +493 -0
- package/src/maquette/projection.test.ts +366 -0
- package/src/maquette/projection.ts +666 -666
- package/src/maquette/projector.test.ts +351 -0
- package/src/maquette/projector.ts +200 -200
- package/src/sample/mixin/highlight.tsx +33 -32
- package/src/sample/sample.tsx +167 -7
- package/src/test-setup.ts +85 -0
- package/src/test-utils.ts +223 -0
- package/tsconfig.json +1 -0
- package/vitest.config.ts +41 -0
- package/webpack.config.js +1 -1
package/src/maquette/jsx.ts
CHANGED
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
import { VNode, VNodeChild, VNodeProperties } from "./interfaces";
|
|
2
|
-
|
|
3
|
-
declare global {
|
|
4
|
-
function jsx(
|
|
5
|
-
tagName: string,
|
|
6
|
-
properties: VNodeProperties | null,
|
|
7
|
-
...children: (VNode | string)[]
|
|
8
|
-
): VNode;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
let toTextVNode = (data: any): VNode => {
|
|
12
|
-
return {
|
|
13
|
-
vnodeSelector: "",
|
|
14
|
-
properties: undefined,
|
|
15
|
-
children: undefined,
|
|
16
|
-
text: data.toString(),
|
|
17
|
-
domNode: null,
|
|
18
|
-
};
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
let appendChildren = (insertions: any[], main: VNode[]) => {
|
|
22
|
-
for (let i = 0, length = insertions.length; i < length; i++) {
|
|
23
|
-
let item = insertions[i];
|
|
24
|
-
if (Array.isArray(item)) {
|
|
25
|
-
appendChildren(item, main);
|
|
26
|
-
} else {
|
|
27
|
-
if (item !== null && item !== undefined && item !== false) {
|
|
28
|
-
if (!item.hasOwnProperty("vnodeSelector")) {
|
|
29
|
-
item = toTextVNode(item);
|
|
30
|
-
}
|
|
31
|
-
main.push(item);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export let jsx = (
|
|
38
|
-
tagName: string,
|
|
39
|
-
properties: VNodeProperties | null,
|
|
40
|
-
...childNodes: VNodeChild[]
|
|
41
|
-
): VNode => {
|
|
42
|
-
if (childNodes.length === 1 && typeof childNodes[0] === "string") {
|
|
43
|
-
return {
|
|
44
|
-
vnodeSelector: tagName,
|
|
45
|
-
properties: properties || undefined,
|
|
46
|
-
children: undefined,
|
|
47
|
-
text: childNodes[0],
|
|
48
|
-
domNode: null,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
let children: VNode[] = [];
|
|
52
|
-
appendChildren(childNodes, children);
|
|
53
|
-
return {
|
|
54
|
-
vnodeSelector: tagName,
|
|
55
|
-
properties: properties || undefined,
|
|
56
|
-
children: children,
|
|
57
|
-
text: undefined,
|
|
58
|
-
domNode: null,
|
|
59
|
-
};
|
|
60
|
-
};
|
|
61
|
-
|
|
1
|
+
import { VNode, VNodeChild, VNodeProperties } from "./interfaces";
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
function jsx(
|
|
5
|
+
tagName: string,
|
|
6
|
+
properties: VNodeProperties | null,
|
|
7
|
+
...children: (VNode | string)[]
|
|
8
|
+
): VNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let toTextVNode = (data: any): VNode => {
|
|
12
|
+
return {
|
|
13
|
+
vnodeSelector: "",
|
|
14
|
+
properties: undefined,
|
|
15
|
+
children: undefined,
|
|
16
|
+
text: data.toString(),
|
|
17
|
+
domNode: null,
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
let appendChildren = (insertions: any[], main: VNode[]) => {
|
|
22
|
+
for (let i = 0, length = insertions.length; i < length; i++) {
|
|
23
|
+
let item = insertions[i];
|
|
24
|
+
if (Array.isArray(item)) {
|
|
25
|
+
appendChildren(item, main);
|
|
26
|
+
} else {
|
|
27
|
+
if (item !== null && item !== undefined && item !== false) {
|
|
28
|
+
if (!item.hasOwnProperty("vnodeSelector")) {
|
|
29
|
+
item = toTextVNode(item);
|
|
30
|
+
}
|
|
31
|
+
main.push(item);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export let jsx = (
|
|
38
|
+
tagName: string,
|
|
39
|
+
properties: VNodeProperties | null,
|
|
40
|
+
...childNodes: VNodeChild[]
|
|
41
|
+
): VNode => {
|
|
42
|
+
if (childNodes.length === 1 && typeof childNodes[0] === "string") {
|
|
43
|
+
return {
|
|
44
|
+
vnodeSelector: tagName,
|
|
45
|
+
properties: properties || undefined,
|
|
46
|
+
children: undefined,
|
|
47
|
+
text: childNodes[0],
|
|
48
|
+
domNode: null,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
let children: VNode[] = [];
|
|
52
|
+
appendChildren(childNodes, children);
|
|
53
|
+
return {
|
|
54
|
+
vnodeSelector: tagName,
|
|
55
|
+
properties: properties || undefined,
|
|
56
|
+
children: children,
|
|
57
|
+
text: undefined,
|
|
58
|
+
domNode: null,
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Maquette mapping module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import { createMapping } from './mapping.js';
|
|
7
|
+
|
|
8
|
+
describe('Maquette mapping', () => {
|
|
9
|
+
describe('createMapping', () => {
|
|
10
|
+
interface TestSource {
|
|
11
|
+
id: number;
|
|
12
|
+
name: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface TestTarget {
|
|
16
|
+
sourceId: number;
|
|
17
|
+
displayName: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
it('should create a mapping instance', () => {
|
|
21
|
+
const mapping = createMapping<TestSource, TestTarget>(
|
|
22
|
+
(source) => source.id,
|
|
23
|
+
(source) => ({ sourceId: source.id, displayName: source.name }),
|
|
24
|
+
(source, target) => {
|
|
25
|
+
target.displayName = source.name;
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
expect(mapping).toBeDefined();
|
|
30
|
+
expect(mapping.results).toBeDefined();
|
|
31
|
+
expect(mapping.map).toBeDefined();
|
|
32
|
+
expect(Array.isArray(mapping.results)).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should create initial results from sources', () => {
|
|
36
|
+
const mapping = createMapping<TestSource, TestTarget>(
|
|
37
|
+
(source) => source.id,
|
|
38
|
+
(source) => ({ sourceId: source.id, displayName: source.name }),
|
|
39
|
+
(source, target) => {
|
|
40
|
+
target.displayName = source.name;
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const sources = [
|
|
45
|
+
{ id: 1, name: 'Alice' },
|
|
46
|
+
{ id: 2, name: 'Bob' },
|
|
47
|
+
{ id: 3, name: 'Charlie' },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
mapping.map(sources);
|
|
51
|
+
|
|
52
|
+
expect(mapping.results.length).toBe(3);
|
|
53
|
+
expect(mapping.results[0].sourceId).toBe(1);
|
|
54
|
+
expect(mapping.results[0].displayName).toBe('Alice');
|
|
55
|
+
expect(mapping.results[1].sourceId).toBe(2);
|
|
56
|
+
expect(mapping.results[2].sourceId).toBe(3);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should update existing results when sources change', () => {
|
|
60
|
+
let createCount = 0;
|
|
61
|
+
let updateCount = 0;
|
|
62
|
+
|
|
63
|
+
const mapping = createMapping<TestSource, TestTarget>(
|
|
64
|
+
(source) => source.id,
|
|
65
|
+
(source) => {
|
|
66
|
+
createCount++;
|
|
67
|
+
return { sourceId: source.id, displayName: source.name };
|
|
68
|
+
},
|
|
69
|
+
(source, target) => {
|
|
70
|
+
updateCount++;
|
|
71
|
+
target.displayName = source.name;
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Initial mapping
|
|
76
|
+
mapping.map([
|
|
77
|
+
{ id: 1, name: 'Alice' },
|
|
78
|
+
{ id: 2, name: 'Bob' },
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
expect(createCount).toBe(2);
|
|
82
|
+
expect(updateCount).toBe(0);
|
|
83
|
+
|
|
84
|
+
// Update with same keys but different names
|
|
85
|
+
mapping.map([
|
|
86
|
+
{ id: 1, name: 'Alice Updated' },
|
|
87
|
+
{ id: 2, name: 'Bob Updated' },
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
expect(createCount).toBe(2); // No new creates
|
|
91
|
+
expect(updateCount).toBe(2); // Both updated
|
|
92
|
+
expect(mapping.results[0].displayName).toBe('Alice Updated');
|
|
93
|
+
expect(mapping.results[1].displayName).toBe('Bob Updated');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should add new items', () => {
|
|
97
|
+
let createCount = 0;
|
|
98
|
+
|
|
99
|
+
const mapping = createMapping<TestSource, TestTarget>(
|
|
100
|
+
(source) => source.id,
|
|
101
|
+
(source) => {
|
|
102
|
+
createCount++;
|
|
103
|
+
return { sourceId: source.id, displayName: source.name };
|
|
104
|
+
},
|
|
105
|
+
(source, target) => {
|
|
106
|
+
target.displayName = source.name;
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
mapping.map([{ id: 1, name: 'Alice' }]);
|
|
111
|
+
expect(createCount).toBe(1);
|
|
112
|
+
|
|
113
|
+
mapping.map([
|
|
114
|
+
{ id: 1, name: 'Alice' },
|
|
115
|
+
{ id: 2, name: 'Bob' },
|
|
116
|
+
{ id: 3, name: 'Charlie' },
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
expect(createCount).toBe(3);
|
|
120
|
+
expect(mapping.results.length).toBe(3);
|
|
121
|
+
expect(mapping.results[2].sourceId).toBe(3);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should remove items', () => {
|
|
125
|
+
const mapping = createMapping<TestSource, TestTarget>(
|
|
126
|
+
(source) => source.id,
|
|
127
|
+
(source) => ({ sourceId: source.id, displayName: source.name }),
|
|
128
|
+
(source, target) => {
|
|
129
|
+
target.displayName = source.name;
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
mapping.map([
|
|
134
|
+
{ id: 1, name: 'Alice' },
|
|
135
|
+
{ id: 2, name: 'Bob' },
|
|
136
|
+
{ id: 3, name: 'Charlie' },
|
|
137
|
+
]);
|
|
138
|
+
|
|
139
|
+
expect(mapping.results.length).toBe(3);
|
|
140
|
+
|
|
141
|
+
mapping.map([{ id: 2, name: 'Bob' }]);
|
|
142
|
+
|
|
143
|
+
expect(mapping.results.length).toBe(1);
|
|
144
|
+
expect(mapping.results[0].sourceId).toBe(2);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should handle reordering of items', () => {
|
|
148
|
+
let createCount = 0;
|
|
149
|
+
let updateCount = 0;
|
|
150
|
+
|
|
151
|
+
const mapping = createMapping<TestSource, TestTarget>(
|
|
152
|
+
(source) => source.id,
|
|
153
|
+
(source) => {
|
|
154
|
+
createCount++;
|
|
155
|
+
return { sourceId: source.id, displayName: source.name };
|
|
156
|
+
},
|
|
157
|
+
(source, target) => {
|
|
158
|
+
updateCount++;
|
|
159
|
+
target.displayName = source.name;
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
mapping.map([
|
|
164
|
+
{ id: 1, name: 'Alice' },
|
|
165
|
+
{ id: 2, name: 'Bob' },
|
|
166
|
+
{ id: 3, name: 'Charlie' },
|
|
167
|
+
]);
|
|
168
|
+
|
|
169
|
+
const originalResults = [...mapping.results];
|
|
170
|
+
|
|
171
|
+
// Reverse order
|
|
172
|
+
mapping.map([
|
|
173
|
+
{ id: 3, name: 'Charlie' },
|
|
174
|
+
{ id: 2, name: 'Bob' },
|
|
175
|
+
{ id: 1, name: 'Alice' },
|
|
176
|
+
]);
|
|
177
|
+
|
|
178
|
+
// Should reuse existing results, not create new ones
|
|
179
|
+
expect(createCount).toBe(3);
|
|
180
|
+
expect(mapping.results.length).toBe(3);
|
|
181
|
+
expect(mapping.results[0].sourceId).toBe(3);
|
|
182
|
+
expect(mapping.results[1].sourceId).toBe(2);
|
|
183
|
+
expect(mapping.results[2].sourceId).toBe(1);
|
|
184
|
+
|
|
185
|
+
// Results should be reordered, not recreated
|
|
186
|
+
expect(mapping.results[0]).toBe(originalResults[2]);
|
|
187
|
+
expect(mapping.results[1]).toBe(originalResults[1]);
|
|
188
|
+
expect(mapping.results[2]).toBe(originalResults[0]);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should handle mixed add, remove, reorder operations', () => {
|
|
192
|
+
const mapping = createMapping<TestSource, TestTarget>(
|
|
193
|
+
(source) => source.id,
|
|
194
|
+
(source) => ({ sourceId: source.id, displayName: source.name }),
|
|
195
|
+
(source, target) => {
|
|
196
|
+
target.displayName = source.name;
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
mapping.map([
|
|
201
|
+
{ id: 1, name: 'Alice' },
|
|
202
|
+
{ id: 2, name: 'Bob' },
|
|
203
|
+
{ id: 3, name: 'Charlie' },
|
|
204
|
+
]);
|
|
205
|
+
|
|
206
|
+
mapping.map([
|
|
207
|
+
{ id: 4, name: 'David' }, // New
|
|
208
|
+
{ id: 2, name: 'Bob' }, // Existing, reordered
|
|
209
|
+
{ id: 5, name: 'Eve' }, // New
|
|
210
|
+
]);
|
|
211
|
+
|
|
212
|
+
expect(mapping.results.length).toBe(3);
|
|
213
|
+
expect(mapping.results[0].sourceId).toBe(4);
|
|
214
|
+
expect(mapping.results[1].sourceId).toBe(2);
|
|
215
|
+
expect(mapping.results[2].sourceId).toBe(5);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should handle string keys', () => {
|
|
219
|
+
interface StringKeySource {
|
|
220
|
+
key: string;
|
|
221
|
+
value: number;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const mapping = createMapping<StringKeySource, { val: number }>(
|
|
225
|
+
(source) => source.key,
|
|
226
|
+
(source) => ({ val: source.value }),
|
|
227
|
+
(source, target) => {
|
|
228
|
+
target.val = source.value;
|
|
229
|
+
}
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
mapping.map([
|
|
233
|
+
{ key: 'a', value: 1 },
|
|
234
|
+
{ key: 'b', value: 2 },
|
|
235
|
+
]);
|
|
236
|
+
|
|
237
|
+
expect(mapping.results.length).toBe(2);
|
|
238
|
+
expect(mapping.results[0].val).toBe(1);
|
|
239
|
+
expect(mapping.results[1].val).toBe(2);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('should pass correct index to createResult and updateResult', () => {
|
|
243
|
+
const createdIndexes: number[] = [];
|
|
244
|
+
const updatedIndexes: number[] = [];
|
|
245
|
+
|
|
246
|
+
const mapping = createMapping<TestSource, TestTarget>(
|
|
247
|
+
(source) => source.id,
|
|
248
|
+
(source, index) => {
|
|
249
|
+
createdIndexes.push(index);
|
|
250
|
+
return { sourceId: source.id, displayName: source.name };
|
|
251
|
+
},
|
|
252
|
+
(source, target, index) => {
|
|
253
|
+
updatedIndexes.push(index);
|
|
254
|
+
target.displayName = source.name;
|
|
255
|
+
}
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
mapping.map([
|
|
259
|
+
{ id: 1, name: 'Alice' },
|
|
260
|
+
{ id: 2, name: 'Bob' },
|
|
261
|
+
{ id: 3, name: 'Charlie' },
|
|
262
|
+
]);
|
|
263
|
+
|
|
264
|
+
expect(createdIndexes).toEqual([0, 1, 2]);
|
|
265
|
+
|
|
266
|
+
mapping.map([
|
|
267
|
+
{ id: 1, name: 'Alice' },
|
|
268
|
+
{ id: 2, name: 'Bob' },
|
|
269
|
+
{ id: 3, name: 'Charlie' },
|
|
270
|
+
]);
|
|
271
|
+
|
|
272
|
+
expect(updatedIndexes).toEqual([0, 1, 2]);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should handle empty arrays', () => {
|
|
276
|
+
const mapping = createMapping<TestSource, TestTarget>(
|
|
277
|
+
(source) => source.id,
|
|
278
|
+
(source) => ({ sourceId: source.id, displayName: source.name }),
|
|
279
|
+
(source, target) => {
|
|
280
|
+
target.displayName = source.name;
|
|
281
|
+
}
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
mapping.map([]);
|
|
285
|
+
expect(mapping.results.length).toBe(0);
|
|
286
|
+
|
|
287
|
+
mapping.map([{ id: 1, name: 'Alice' }]);
|
|
288
|
+
expect(mapping.results.length).toBe(1);
|
|
289
|
+
|
|
290
|
+
mapping.map([]);
|
|
291
|
+
expect(mapping.results.length).toBe(0);
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
});
|
package/src/maquette/mapping.ts
CHANGED
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
import { Mapping } from "./interfaces";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Creates a {@link Mapping} instance that keeps an array of result objects synchronized with an array of source objects.
|
|
5
|
-
* See {@link http://maquettejs.org/docs/arrays.html Working with arrays}.
|
|
6
|
-
*
|
|
7
|
-
* @param <Source> The type of source items. A database-record for instance.
|
|
8
|
-
* @param <Target> The type of target items. A [[MaquetteComponent]] for instance.
|
|
9
|
-
* @param getSourceKey `function(source)` that must return a key to identify each source object. The result must either be a string or a number.
|
|
10
|
-
* @param createResult `function(source, index)` that must create a new result object from a given source. This function is identical
|
|
11
|
-
* to the `callback` argument in `Array.map(callback)`.
|
|
12
|
-
* @param updateResult `function(source, target, index)` that updates a result to an updated source.
|
|
13
|
-
*/
|
|
14
|
-
export let createMapping = <Source, Target>(
|
|
15
|
-
getSourceKey: (source: Source) => string | number,
|
|
16
|
-
createResult: (source: Source, index: number) => Target,
|
|
17
|
-
updateResult: (source: Source, target: Target, index: number) => void
|
|
18
|
-
): Mapping<Source, Target> => {
|
|
19
|
-
let keys = [] as unknown[];
|
|
20
|
-
let results = [] as Target[];
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
results: results,
|
|
24
|
-
map: (newSources: Source[]) => {
|
|
25
|
-
let newKeys = newSources.map(getSourceKey);
|
|
26
|
-
let oldTargets = results.slice();
|
|
27
|
-
let oldIndex = 0;
|
|
28
|
-
for (let i = 0; i < newSources.length; i++) {
|
|
29
|
-
let source = newSources[i];
|
|
30
|
-
let sourceKey = newKeys[i];
|
|
31
|
-
if (sourceKey === keys[oldIndex]) {
|
|
32
|
-
results[i] = oldTargets[oldIndex];
|
|
33
|
-
updateResult(source, oldTargets[oldIndex], i);
|
|
34
|
-
oldIndex++;
|
|
35
|
-
} else {
|
|
36
|
-
let found = false;
|
|
37
|
-
for (let j = 1; j < keys.length + 1; j++) {
|
|
38
|
-
let searchIndex = (oldIndex + j) % keys.length;
|
|
39
|
-
if (keys[searchIndex] === sourceKey) {
|
|
40
|
-
results[i] = oldTargets[searchIndex];
|
|
41
|
-
updateResult(newSources[i], oldTargets[searchIndex], i);
|
|
42
|
-
oldIndex = searchIndex + 1;
|
|
43
|
-
found = true;
|
|
44
|
-
break;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
if (!found) {
|
|
48
|
-
results[i] = createResult(source, i);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
results.length = newSources.length;
|
|
53
|
-
keys = newKeys;
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
};
|
|
1
|
+
import { Mapping } from "./interfaces";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a {@link Mapping} instance that keeps an array of result objects synchronized with an array of source objects.
|
|
5
|
+
* See {@link http://maquettejs.org/docs/arrays.html Working with arrays}.
|
|
6
|
+
*
|
|
7
|
+
* @param <Source> The type of source items. A database-record for instance.
|
|
8
|
+
* @param <Target> The type of target items. A [[MaquetteComponent]] for instance.
|
|
9
|
+
* @param getSourceKey `function(source)` that must return a key to identify each source object. The result must either be a string or a number.
|
|
10
|
+
* @param createResult `function(source, index)` that must create a new result object from a given source. This function is identical
|
|
11
|
+
* to the `callback` argument in `Array.map(callback)`.
|
|
12
|
+
* @param updateResult `function(source, target, index)` that updates a result to an updated source.
|
|
13
|
+
*/
|
|
14
|
+
export let createMapping = <Source, Target>(
|
|
15
|
+
getSourceKey: (source: Source) => string | number,
|
|
16
|
+
createResult: (source: Source, index: number) => Target,
|
|
17
|
+
updateResult: (source: Source, target: Target, index: number) => void
|
|
18
|
+
): Mapping<Source, Target> => {
|
|
19
|
+
let keys = [] as unknown[];
|
|
20
|
+
let results = [] as Target[];
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
results: results,
|
|
24
|
+
map: (newSources: Source[]) => {
|
|
25
|
+
let newKeys = newSources.map(getSourceKey);
|
|
26
|
+
let oldTargets = results.slice();
|
|
27
|
+
let oldIndex = 0;
|
|
28
|
+
for (let i = 0; i < newSources.length; i++) {
|
|
29
|
+
let source = newSources[i];
|
|
30
|
+
let sourceKey = newKeys[i];
|
|
31
|
+
if (sourceKey === keys[oldIndex]) {
|
|
32
|
+
results[i] = oldTargets[oldIndex];
|
|
33
|
+
updateResult(source, oldTargets[oldIndex], i);
|
|
34
|
+
oldIndex++;
|
|
35
|
+
} else {
|
|
36
|
+
let found = false;
|
|
37
|
+
for (let j = 1; j < keys.length + 1; j++) {
|
|
38
|
+
let searchIndex = (oldIndex + j) % keys.length;
|
|
39
|
+
if (keys[searchIndex] === sourceKey) {
|
|
40
|
+
results[i] = oldTargets[searchIndex];
|
|
41
|
+
updateResult(newSources[i], oldTargets[searchIndex], i);
|
|
42
|
+
oldIndex = searchIndex + 1;
|
|
43
|
+
found = true;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (!found) {
|
|
48
|
+
results[i] = createResult(source, i);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
results.length = newSources.length;
|
|
53
|
+
keys = newKeys;
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
};
|