@pcg/dynamic-components 1.0.0-alpha.0

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.
Files changed (60) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/index.d.ts +1816 -0
  3. package/dist/index.js +1564 -0
  4. package/dist/index.js.map +1 -0
  5. package/eslint.config.cjs +14 -0
  6. package/package.json +30 -0
  7. package/src/assertions/basic.ts +58 -0
  8. package/src/assertions/containers.ts +76 -0
  9. package/src/assertions/index.ts +6 -0
  10. package/src/assertions/paths.ts +12 -0
  11. package/src/assertions/rich-text.ts +16 -0
  12. package/src/assertions/yjs.ts +25 -0
  13. package/src/data-objects/data-object.ts +34 -0
  14. package/src/data-objects/index.ts +3 -0
  15. package/src/data-objects/rich-text.ts +38 -0
  16. package/src/dynamic-components/fractional-indexing.ts +321 -0
  17. package/src/dynamic-components/index.ts +6 -0
  18. package/src/dynamic-components/paths.ts +194 -0
  19. package/src/dynamic-components/registry/chats.ts +24 -0
  20. package/src/dynamic-components/registry/content.ts +118 -0
  21. package/src/dynamic-components/registry/forms.ts +525 -0
  22. package/src/dynamic-components/registry/index.ts +6 -0
  23. package/src/dynamic-components/registry/layout.ts +86 -0
  24. package/src/dynamic-components/registry/uikit-dynamic-component.ts +84 -0
  25. package/src/dynamic-components/tools.ts +195 -0
  26. package/src/dynamic-components/types.ts +237 -0
  27. package/src/index.ts +7 -0
  28. package/src/paths/array-keys.ts +164 -0
  29. package/src/paths/array-ops.ts +124 -0
  30. package/src/paths/basic-ops.ts +181 -0
  31. package/src/paths/constants.ts +1 -0
  32. package/src/paths/index.ts +7 -0
  33. package/src/paths/tools.ts +42 -0
  34. package/src/paths/types.ts +133 -0
  35. package/src/y-components/index.ts +3 -0
  36. package/src/y-components/tools.ts +234 -0
  37. package/src/y-components/types.ts +19 -0
  38. package/src/y-tools/array-path-ops.ts +240 -0
  39. package/src/y-tools/basic-path-ops.ts +189 -0
  40. package/src/y-tools/index.ts +6 -0
  41. package/src/y-tools/tools.ts +122 -0
  42. package/src/y-tools/types.ts +32 -0
  43. package/src/y-tools/y-array-keys.ts +47 -0
  44. package/tests/assertions/basic-types.test.ts +78 -0
  45. package/tests/assertions/containers.test.ts +72 -0
  46. package/tests/assertions/paths.test.ts +23 -0
  47. package/tests/assertions/yjs.test.ts +33 -0
  48. package/tests/dynamic-components/paths.test.ts +171 -0
  49. package/tests/dynamic-components/tools.test.ts +121 -0
  50. package/tests/paths/array-keys.test.ts +182 -0
  51. package/tests/paths/array-ops.test.ts +164 -0
  52. package/tests/paths/basic-ops.test.ts +263 -0
  53. package/tests/paths/tools.test.ts +55 -0
  54. package/tests/y-components/tools.test.ts +198 -0
  55. package/tests/y-tools/array-base-ops.test.ts +55 -0
  56. package/tests/y-tools/array-path-ops.test.ts +95 -0
  57. package/tsconfig.json +13 -0
  58. package/tsconfig.lib.json +13 -0
  59. package/tsdown.config.ts +18 -0
  60. package/vitest.config.ts +19 -0
@@ -0,0 +1,182 @@
1
+ import {
2
+ describe, expect, test,
3
+ } from 'vitest';
4
+ import {
5
+ createIdArrayKey,
6
+ createPositionArrayKey, extractIdFromArrayKey, extractPositionFromArrayKey, getIndexByArrayKey, isArrayKey, isIdArrayKey, isPositionArrayKey,
7
+ } from '../../src/paths/index.js';
8
+
9
+ describe('Position Array Key', () => {
10
+ describe('isPositionArrayKey', () => {
11
+ test.each([
12
+ 'pos:a0',
13
+ 'pos:Zz',
14
+ ])('must return true for correct position array key', (key) => {
15
+ expect(isPositionArrayKey(key)).toBeTruthy();
16
+ });
17
+
18
+ test.each([
19
+ 0,
20
+ null,
21
+ 'xx2245x',
22
+ 'ps:mpdf:xxxxx',
23
+ 'pos2355',
24
+ ])('must return false for invalid position array key', (key) => {
25
+ expect(isPositionArrayKey(key)).toBeFalsy();
26
+ });
27
+ });
28
+
29
+ describe('createPositionArrayKey', () => {
30
+ test('must create position array key', () => {
31
+ const key = createPositionArrayKey('a0');
32
+ expect(isPositionArrayKey(key)).toBeTruthy();
33
+ });
34
+ });
35
+
36
+ describe('extractPositionFromArrayKey', () => {
37
+ test('must extract position from array key', () => {
38
+ const key = createPositionArrayKey('a0');
39
+ expect(extractPositionFromArrayKey(key)).toBe('a0');
40
+ });
41
+
42
+ test('must throw error on invalid array key', () => {
43
+ expect(() => {
44
+ extractPositionFromArrayKey('invalid_key');
45
+ }).toThrowError(/^Invalid array key/);
46
+ });
47
+ });
48
+ });
49
+
50
+ describe('Id Array Key', () => {
51
+ describe('isIdArrayKey', () => {
52
+ test.each([
53
+ 'id:xxx',
54
+ 'id:mpdf:xxxxx',
55
+ ])('must return true for correct id array key', (key) => {
56
+ expect(isIdArrayKey(key)).toBeTruthy();
57
+ });
58
+
59
+ test.each([
60
+ 0,
61
+ null,
62
+ 'xx2245x',
63
+ 'pos:2355',
64
+ ])('must return false for invalid id array key', (key) => {
65
+ expect(isIdArrayKey(key)).toBeFalsy();
66
+ });
67
+ });
68
+
69
+ describe('createIdArrayKey', () => {
70
+ test('must create id array key', () => {
71
+ const key = createIdArrayKey('xxx');
72
+ expect(isIdArrayKey(key)).toBeTruthy();
73
+ });
74
+ });
75
+
76
+ describe('extractIdFromArrayKey', () => {
77
+ test('must extract id from array key', () => {
78
+ const key = createIdArrayKey('xxx');
79
+ expect(extractIdFromArrayKey(key)).toBe('xxx');
80
+ });
81
+
82
+ test('must throw error on invalid array key', () => {
83
+ expect(() => {
84
+ extractIdFromArrayKey('invalid_key');
85
+ }).toThrowError(/^Invalid array key/);
86
+ });
87
+ });
88
+ });
89
+
90
+ describe('isArrayKey', () => {
91
+ test.each([
92
+ 'pos:a0',
93
+ 'pos:Zz',
94
+ 'id:xxx',
95
+ 'id:mpdf:xxx',
96
+ ])('must return true for correct array key', (key) => {
97
+ expect(isArrayKey(key)).toBeTruthy();
98
+ });
99
+
100
+ test.each([
101
+ 0,
102
+ null,
103
+ 'xx2245x',
104
+ 'ids:xx:xxx',
105
+ 'ps:mpdf:xxxxx',
106
+ 'pos2355',
107
+ ])('must return false for invalid array key', (key) => {
108
+ expect(isArrayKey(key)).toBeFalsy();
109
+ });
110
+ });
111
+
112
+ describe('getIndexByArrayKey', () => {
113
+ test('must throw error on non-array type', () => {
114
+ expect(() => {
115
+ getIndexByArrayKey({
116
+ }, 'pos:a2');
117
+ }).toThrowError(/^Can't use array key in non array type/);
118
+ });
119
+
120
+ test('must extract index from position key', () => {
121
+ const array = [
122
+ {
123
+ position: 'a2',
124
+ },
125
+ {
126
+ position: 'a0',
127
+ },
128
+ {
129
+ position: 'a1',
130
+ },
131
+ ];
132
+
133
+ expect(getIndexByArrayKey(array, 'pos:a0')).toBe(1);
134
+ });
135
+
136
+ test('must extract index from id key', () => {
137
+ const array = [
138
+ {
139
+ id: 'x',
140
+ },
141
+ {
142
+ id: 'z',
143
+ },
144
+ {
145
+ id: 'y',
146
+ },
147
+ ];
148
+
149
+ expect(getIndexByArrayKey(array, 'id:z')).toBe(1);
150
+ });
151
+
152
+ test('must throw error on unknown array key', () => {
153
+ expect(() => {
154
+ getIndexByArrayKey([], 'type:article');
155
+ }).toThrowError(/^Unknown array key type/);
156
+ });
157
+
158
+ test('must throw error on undefined array item', () => {
159
+ const array = [
160
+ {
161
+ id: 'x',
162
+ position: 'a2',
163
+ },
164
+ {
165
+ id: 'z',
166
+ position: 'a0',
167
+ },
168
+ {
169
+ id: 'y',
170
+ position: 'a1',
171
+ },
172
+ ];
173
+
174
+ expect(() => {
175
+ getIndexByArrayKey(array, 'pos:a3');
176
+ }).toThrowError(/^Can't find index by position/);
177
+
178
+ expect(() => {
179
+ getIndexByArrayKey(array, 'id:q');
180
+ }).toThrowError(/^Can't find index by id/);
181
+ });
182
+ });
@@ -0,0 +1,164 @@
1
+ import { DynamicComponentWithPosition } from '@/dynamic-components/index.js';
2
+ import {
3
+ describe, expect, test,
4
+ } from 'vitest';
5
+ import {
6
+ insertInPath, moveInPath, pullFromPath, pushInPath,
7
+ } from '../../src/paths/index.js';
8
+
9
+ describe('pushInPath', () => {
10
+ test('must push in root array', () => {
11
+ const items: string[] = [];
12
+
13
+ pushInPath(items, [], 'Item');
14
+
15
+ expect(items[0]).toBe('Item');
16
+ });
17
+
18
+ test('must push in array by deep path', () => {
19
+ const object = {
20
+ items: [
21
+ {
22
+ position: 'a0',
23
+ tags: [],
24
+ },
25
+ {
26
+ position: 'a1',
27
+ tags: [],
28
+ },
29
+ ],
30
+ };
31
+
32
+ pushInPath(object, ['items', 'pos:a1', 'tags'], 'New Tag');
33
+
34
+ expect(object.items[1].tags[0]).toBe('New Tag');
35
+ });
36
+
37
+ test('must recreate path and push item', () => {
38
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
+ const object: any = {
40
+ id: 'xxx',
41
+ };
42
+
43
+ pushInPath(object, ['items', 0, 'tags'], 'New Tag');
44
+
45
+ expect(object.items?.[0]?.tags?.[0]).toBe('New Tag');
46
+ });
47
+ });
48
+
49
+ describe('insertInPath', () => {
50
+ test('must push in root array', () => {
51
+ const items: string[] = [
52
+ 'One',
53
+ 'Two',
54
+ 'Three',
55
+ ];
56
+
57
+ insertInPath(items, [], 1, ['Foo', 'Bar']);
58
+
59
+ expect(items).toEqual([
60
+ 'One',
61
+ 'Foo',
62
+ 'Bar',
63
+ 'Two',
64
+ 'Three',
65
+ ]);
66
+
67
+ insertInPath(items, [], 10, ['Baz']);
68
+
69
+ expect(items[items.length - 1]).toBe('Baz');
70
+ });
71
+
72
+ test('must push in array by position key', () => {
73
+ const object = {
74
+ items: [
75
+ {
76
+ position: 'a0',
77
+ },
78
+ {
79
+ position: 'a1',
80
+ },
81
+ ],
82
+ };
83
+
84
+ insertInPath(object, ['items'], 'pos:a0', [{
85
+ position: 'a01',
86
+ tags: [],
87
+ }]);
88
+
89
+ expect(object.items[0].position).toBe('a01');
90
+ });
91
+
92
+ test.only('must push component to root array', () => {
93
+ const components: DynamicComponentWithPosition[] = [];
94
+
95
+ insertInPath(components, [], 0, [{
96
+ id: 'xxx',
97
+ component: 'text-input',
98
+ props: {
99
+ },
100
+ }]);
101
+
102
+ expect(components[0].id).toBe('xxx');
103
+ });
104
+ });
105
+
106
+ describe('pullFromPath', () => {
107
+ test('must pull from array by index', () => {
108
+ const object = {
109
+ items: [
110
+ 'One',
111
+ 'Two',
112
+ 'Three',
113
+ ],
114
+ };
115
+
116
+ pullFromPath(object, ['items'], 1);
117
+
118
+ expect(object.items[1]).toBe('Three');
119
+ });
120
+
121
+ test('must pull from array by id key', () => {
122
+ const object = {
123
+ items: [
124
+ {
125
+ id: 'x',
126
+ },
127
+ {
128
+ id: 'y',
129
+ },
130
+ {
131
+ id: 'z',
132
+ },
133
+ ],
134
+ };
135
+
136
+ pullFromPath(object, ['items'], 'id:y');
137
+
138
+ expect(object.items[1].id).toBe('z');
139
+ });
140
+ });
141
+
142
+ describe('moveInPath', () => {
143
+ // new test for moveInPath
144
+ test('must move item in array by position key', () => {
145
+ const object = {
146
+ items: [
147
+ {
148
+ position: 'a0',
149
+ },
150
+ {
151
+ position: 'a1',
152
+ },
153
+ {
154
+ position: 'a2',
155
+ },
156
+ ],
157
+ };
158
+
159
+ moveInPath(object, ['items'], 'a1', 'a0', 'before');
160
+
161
+ expect(object.items[0].position).toBe('a1');
162
+ expect(object.items[1].position).toBe('a0');
163
+ });
164
+ });
@@ -0,0 +1,263 @@
1
+ import {
2
+ describe, expect, test,
3
+ } from 'vitest';
4
+ import {
5
+ existsInPath,
6
+ getFromPath,
7
+ isSamePath,
8
+ setInPath, unsetPath,
9
+ } from '../../src/paths/index.js';
10
+
11
+ describe('getFromPath', () => {
12
+ test('must return root item on root path', () => {
13
+ const array = [
14
+ {
15
+ id: 'xx',
16
+ },
17
+ ];
18
+
19
+ expect(Array.isArray(getFromPath(array, []))).toBeTruthy();
20
+ });
21
+
22
+ test('must return undefined on non container argument', () => {
23
+ expect(getFromPath(undefined, [])).toBeUndefined();
24
+ });
25
+
26
+ test('must get item from simple path', () => {
27
+ const object = {
28
+ items: [
29
+ {
30
+ id: 'xxx',
31
+ },
32
+ ],
33
+ };
34
+
35
+ expect(getFromPath(object, ['items', 0, 'id'])).toBe('xxx');
36
+ });
37
+
38
+ test('must get item from complex path', () => {
39
+ const object = {
40
+ items: [
41
+ {
42
+ position: 'a0',
43
+ tags: [{
44
+ id: 'tag1',
45
+ alias: 'Foo',
46
+ }],
47
+ },
48
+ {
49
+ position: 'a1',
50
+ tags: [{
51
+ id: 'tag2',
52
+ alias: 'Bar',
53
+ }, {
54
+ id: 'tag3',
55
+ alias: 'Baz',
56
+ }],
57
+ },
58
+ ],
59
+ };
60
+
61
+ expect(getFromPath(object, ['items', 'pos:a1', 'tags', 'id:tag2', 'alias'])).toBe('Bar');
62
+ });
63
+ });
64
+
65
+ describe('setInPath', () => {
66
+ test('must set item in array', () => {
67
+ const object = {
68
+ items: [
69
+ 'One',
70
+ 'Two',
71
+ 'Three',
72
+ ],
73
+ };
74
+
75
+ setInPath(object, ['items', 1], 'Updated');
76
+ expect(getFromPath(object, ['items', 1])).toBe('Updated');
77
+ });
78
+
79
+ test('must set in simple path', () => {
80
+ const object = {
81
+ items: [
82
+ {
83
+ id: 'xxx',
84
+ title: 'Hello',
85
+ },
86
+ ],
87
+ };
88
+
89
+ setInPath(object, ['items', 0, 'title'], 'World');
90
+ expect(getFromPath(object, ['items', 0, 'title'])).toBe('World');
91
+ });
92
+
93
+ test('must set in complex path', () => {
94
+ const object = {
95
+ items: [
96
+ {
97
+ position: 'a0',
98
+ tags: [{
99
+ id: 'tag1',
100
+ alias: 'Foo',
101
+ }],
102
+ },
103
+ {
104
+ position: 'a1',
105
+ tags: [{
106
+ id: 'tag2',
107
+ alias: 'Bar',
108
+ }, {
109
+ id: 'tag3',
110
+ alias: 'Baz',
111
+ }],
112
+ },
113
+ ],
114
+ };
115
+
116
+ setInPath(object, ['items', 'pos:a1', 'tags', 'id:tag2', 'alias'], 'Updated');
117
+ expect(getFromPath(object, ['items', 'pos:a1', 'tags', 'id:tag2', 'alias'])).toBe('Updated');
118
+ });
119
+
120
+ test('must recreate complex path and set', () => {
121
+ const object = {
122
+ };
123
+
124
+ setInPath(object, ['items', 0, 'tags', 0, 'alias'], 'Foo');
125
+ expect(object).toMatchObject({
126
+ items: [
127
+ {
128
+ tags: [
129
+ {
130
+ alias: 'Foo',
131
+ },
132
+ ],
133
+ },
134
+ ],
135
+ });
136
+ });
137
+ });
138
+
139
+ describe('unsetPath', () => {
140
+ test('must unset value in simple path', () => {
141
+ const object = {
142
+ items: [
143
+ {
144
+ id: 'xxx',
145
+ title: 'Hello',
146
+ },
147
+ ],
148
+ };
149
+
150
+ expect(getFromPath(object, ['items', 0, 'title'])).toBe('Hello');
151
+
152
+ unsetPath(object, ['items', 0, 'title']);
153
+
154
+ expect(getFromPath(object, ['items', 0, 'title'])).toBeUndefined();
155
+ });
156
+
157
+ test('must unset in complex path', () => {
158
+ const object = {
159
+ items: [
160
+ {
161
+ position: 'a0',
162
+ tags: [{
163
+ id: 'tag1',
164
+ alias: 'Foo',
165
+ }],
166
+ },
167
+ {
168
+ position: 'a1',
169
+ tags: [{
170
+ id: 'tag2',
171
+ alias: 'Bar',
172
+ }, {
173
+ id: 'tag3',
174
+ alias: 'Baz',
175
+ }],
176
+ },
177
+ ],
178
+ };
179
+
180
+ expect(getFromPath(object, ['items', 'pos:a1', 'tags', 'id:tag3', 'alias'])).toBe('Baz');
181
+
182
+ unsetPath(object, ['items', 'pos:a1', 'tags', 'id:tag3', 'alias']);
183
+
184
+ expect(getFromPath(object, ['items', 'pos:a1', 'tags', 'id:tag3', 'alias'])).toBeUndefined();
185
+ });
186
+ });
187
+
188
+ describe('existsInPath', () => {
189
+ test('must return true for existing simple path', () => {
190
+ const object = {
191
+ items: [
192
+ {
193
+ id: 'xxx',
194
+ title: 'Hello',
195
+ },
196
+ ],
197
+ };
198
+
199
+ expect(existsInPath(object, ['items', 0, 'title'])).toBe(true);
200
+ });
201
+
202
+ test('must return false for non-existing simple path', () => {
203
+ const object = {
204
+ items: [
205
+ {
206
+ id: 'xxx',
207
+ },
208
+ ],
209
+ };
210
+
211
+ expect(existsInPath(object, ['items', 0, 'title'])).toBe(false);
212
+ });
213
+
214
+ test('must return true for existing complex path', () => {
215
+ const object = {
216
+ items: [
217
+ {
218
+ position: 'a1',
219
+ tags: [{
220
+ id: 'tag2',
221
+ alias: 'Bar',
222
+ }],
223
+ },
224
+ ],
225
+ };
226
+
227
+ expect(existsInPath(object, ['items', 'pos:a1', 'tags', 'id:tag2', 'alias'])).toBe(true);
228
+ });
229
+
230
+ test('must return false for undefined object', () => {
231
+ expect(existsInPath(undefined, ['items', 0, 'title'])).toBe(false);
232
+ });
233
+
234
+ test('must return true for root path on existing object', () => {
235
+ const object = {
236
+ items: [],
237
+ };
238
+
239
+ expect(existsInPath(object, [])).toBe(true);
240
+ });
241
+ });
242
+
243
+ describe('isSamePath', () => {
244
+ test('must return true for identical simple paths', () => {
245
+ expect(isSamePath(['items', 0, 'name'], ['items', 0, 'name'])).toBe(true);
246
+ });
247
+
248
+ test('must return false for different simple paths', () => {
249
+ expect(isSamePath(['items', 0, 'name'], ['items', 1, 'name'])).toBe(false);
250
+ });
251
+
252
+ test('must return false for paths of different lengths', () => {
253
+ expect(isSamePath(['items', 0], ['items', 0, 'name'])).toBe(false);
254
+ });
255
+
256
+ test('must return true for empty paths', () => {
257
+ expect(isSamePath([], [])).toBe(true);
258
+ });
259
+
260
+ test('must return false when comparing empty path with non-empty path', () => {
261
+ expect(isSamePath([], ['items'])).toBe(false);
262
+ });
263
+ });
@@ -0,0 +1,55 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import {
3
+ joinPath,
4
+ sortByPosition,
5
+ } from '../../src/paths/tools.js';
6
+
7
+ describe('joinPath', () => {
8
+ test('must join one path item', () => {
9
+ expect(joinPath([0], 'props')).toEqual([0, 'props']);
10
+ });
11
+
12
+ test('must join multiple path items', () => {
13
+ expect(joinPath([0], 'props', 'components')).toEqual([0, 'props', 'components']);
14
+ });
15
+
16
+ test('must throw error on invalid path', () => {
17
+ expect(() => {
18
+ // @ts-expect-error for test
19
+ joinPath(0, 'props');
20
+ }).toThrowError(/^path must be array/);
21
+ });
22
+
23
+ test('must throw error on invalid path item', () => {
24
+ expect(() => {
25
+ // @ts-expect-error for test
26
+ joinPath([0], new RegExp('props'));
27
+ }).toThrowError(/^Invalid path item/);
28
+ });
29
+ });
30
+
31
+ describe('sortByPosition', () => {
32
+ test('must sort by position', () => {
33
+ const unsorted = [{
34
+ position: 'a2',
35
+ }, {
36
+ position: 'a2',
37
+ }, {
38
+ position: 'a0',
39
+ }, {
40
+ position: 'a1',
41
+ }];
42
+
43
+ const sorted = [{
44
+ position: 'a0',
45
+ }, {
46
+ position: 'a1',
47
+ }, {
48
+ position: 'a2',
49
+ }, {
50
+ position: 'a2',
51
+ }];
52
+
53
+ expect(unsorted.sort((a, b) => sortByPosition(a.position, b.position))).toEqual(sorted);
54
+ });
55
+ });