@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,195 @@
1
+ import * as Y from 'yjs';
2
+
3
+ import { isRichText } from '../assertions/rich-text.js';
4
+ import { generateKeyBetween } from './fractional-indexing.js';
5
+ import {
6
+ DynamicComponent, DynamicComponentWithPosition, Point,
7
+ } from './types.js';
8
+
9
+ // This alphabet uses `A-Za-z0-9_-` symbols.
10
+ // The order of characters is optimized for better gzip and brotli compression.
11
+ // References to the same file (works both for gzip and brotli):
12
+ // `'use`, `andom`, and `rict'`
13
+ // References to the brotli default dictionary:
14
+ // `-26T`, `1983`, `40px`, `75px`, `bush`, `jack`, `mind`, `very`, and `wolf`
15
+ export const urlAlphabet =
16
+ 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict';
17
+
18
+ export const customAlphabet = (alphabet: string, defaultSize = 21) => {
19
+ return (size = defaultSize) => {
20
+ let id = '';
21
+ // A compact alternative for `for (var i = 0; i < step; i++)`.
22
+ let i = size;
23
+ while (i--) {
24
+ // `| 0` is more compact and faster than `Math.floor()`.
25
+ // eslint-disable-next-line no-bitwise
26
+ id += alphabet[(Math.random() * alphabet.length) | 0];
27
+ }
28
+
29
+ return id;
30
+ };
31
+ };
32
+
33
+ export const uid = customAlphabet('1234567890abcdefg', 5);
34
+
35
+ /**
36
+ * Gets the path to a property in a component.
37
+ * @param {(string | number)[]} componentPath - The path to the component.
38
+ * @param {string} name - The name of the property.
39
+ * @returns {(string | number)[]} Returns the path to the property.
40
+ * @example
41
+ * getComponentPropertyPath(['components', 0], 'name'); // Returns ['components', 0, 'props', 'name']
42
+ */
43
+ export const getComponentPropertyPath = (componentPath: (string | number)[], name: string) => [...componentPath, 'props', name];
44
+
45
+ /**
46
+ * Creates a copy of a dynamic component.
47
+ * @param {DynamicComponent} component - The component to copy.
48
+ * @param {Y.Doc} yDoc - The Yjs document to update with the copied component.
49
+ * @returns {DynamicComponent} Returns the copied component.
50
+ */
51
+ export const copyDynamicComponent = (component: DynamicComponent, yDoc?: Y.Doc): DynamicComponent => {
52
+ const {
53
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
54
+ id,
55
+
56
+ props,
57
+ coordinates,
58
+ inputs,
59
+ outputs,
60
+ ...extras
61
+ } = component;
62
+
63
+ const componentCopy: DynamicComponent = {
64
+ ...extras,
65
+ id: uid(),
66
+ props: {
67
+ },
68
+ };
69
+
70
+ if (coordinates) {
71
+ componentCopy.coordinates = [
72
+ coordinates[0] + 10,
73
+ coordinates[1] + 10,
74
+ ];
75
+ }
76
+
77
+ if (inputs) {
78
+ componentCopy.inputs = inputs.map((input) => ({
79
+ ...input,
80
+ id: uid(),
81
+ }));
82
+ }
83
+
84
+ if (outputs) {
85
+ componentCopy.outputs = outputs.map((output) => ({
86
+ ...output,
87
+ id: uid(),
88
+ }));
89
+ }
90
+
91
+ for (const [key, value] of Object.entries(props)) {
92
+ if (key === 'components' && Array.isArray(value)) {
93
+ componentCopy.props.components = value.map((cmp) => copyDynamicComponent(cmp));
94
+ } else if (isRichText(value)) {
95
+ const oldKey = `${value.type}:${value.id}`;
96
+
97
+ const newRichText = {
98
+ ...value,
99
+ id: uid(),
100
+ };
101
+
102
+ const newKey = `${value.type}:${newRichText.id}`;
103
+
104
+ if (yDoc) {
105
+ const yXmlFragment = yDoc.getXmlFragment(oldKey);
106
+ const newYXmlFragment = yDoc.getXmlFragment(newKey);
107
+
108
+ const clones: (Y.XmlElement | Y.XmlText)[] = [];
109
+ for (const item of yXmlFragment.toArray()) {
110
+ if (item instanceof Y.XmlElement || item instanceof Y.XmlText) {
111
+ clones.push(item.clone());
112
+ }
113
+ }
114
+
115
+ newYXmlFragment.insert(0, clones);
116
+ }
117
+
118
+ componentCopy.props[key] = newRichText;
119
+ } else {
120
+ componentCopy.props[key] = value;
121
+ }
122
+ }
123
+
124
+ return componentCopy;
125
+ };
126
+
127
+ /**
128
+ * Sorts an array of dynamic components and generates a position for each one.
129
+ * @param {DynamicComponent[]} components - The components to sort.
130
+ * @returns {DynamicComponentWithPosition[]} Returns the sorted components with positions.
131
+ */
132
+ export const sortDynamicComponents = (components: DynamicComponent[]): DynamicComponentWithPosition[] => {
133
+ return components.reduce((acc, component, i) => {
134
+ const prev = acc[i - 1];
135
+
136
+ const {
137
+ position,
138
+ props: {
139
+ components,
140
+ ...baseProps
141
+ },
142
+ ...baseFields
143
+ } = component;
144
+
145
+ const props: DynamicComponentWithPosition['props'] = baseProps;
146
+
147
+ if (components) {
148
+ props.components = sortDynamicComponents(components);
149
+ }
150
+
151
+ acc.push({
152
+ ...baseFields,
153
+ position: position ?? generateKeyBetween(prev?.position ?? null, null),
154
+ props,
155
+ });
156
+
157
+ return acc;
158
+ }, [] as DynamicComponentWithPosition[]);
159
+ };
160
+
161
+ /**
162
+ * Searches for a dynamic component in a tree that matches a condition.
163
+ * @param {DynamicComponent[]} components - The tree of components to search.
164
+ * @param {(cmp: DynamicComponent) => boolean} isMatches - The condition to match.
165
+ * @returns {DynamicComponent | null} Returns the matching component, or null if no match was found.
166
+ */
167
+ export const searchDynamicComponentInTree = (
168
+ components: DynamicComponent[],
169
+ isMatches: (cmp: DynamicComponent) => boolean,
170
+ ): DynamicComponent | null => {
171
+ for (const component of components) {
172
+ if (isMatches(component)) {
173
+ return component;
174
+ }
175
+
176
+ if (Array.isArray(component.props.components)) {
177
+ const result = searchDynamicComponentInTree(component.props.components, isMatches);
178
+ if (result) {
179
+ return result;
180
+ }
181
+ }
182
+ }
183
+
184
+ return null;
185
+ };
186
+
187
+ export const getRelativePoint = (point: Point, relative: Point): Point => [point[0] - relative[0], point[1] - relative[1]];
188
+
189
+ /**
190
+ * Creates a copy of a dynamic component and set its position (fractional-index) rely on the index of the component.
191
+ * @param components - The components without positions.
192
+ */
193
+ export const makeDynamicComponentsWithPosition = (components: DynamicComponent[]): DynamicComponentWithPosition[] => {
194
+ return sortDynamicComponents(components.slice(0));
195
+ };
@@ -0,0 +1,237 @@
1
+ export type Point = [number, number]; // [x, y]
2
+
3
+ export interface DynamicComponentIO {
4
+ id: string;
5
+ name: string;
6
+ }
7
+
8
+ /**
9
+ * Represents a link between two dynamic components.
10
+ */
11
+ export interface DynamicComponentLink {
12
+ /** The unique identifier of the link. */
13
+ id: string;
14
+ /** The input of the link. */
15
+ input: string;
16
+ /** The output of the link. */
17
+ output: string;
18
+ }
19
+
20
+ /**
21
+
22
+ /**
23
+ * ValidationRules interface is used for defining the set of
24
+ * validation constraints for a field in a form or component.
25
+ *
26
+ * @example
27
+ * let rules: ValidationRules = {
28
+ * required: true,
29
+ * min: 1,
30
+ * max: 100,
31
+ * pattern: 'email'
32
+ * };
33
+ */
34
+ export interface ValidationRules {
35
+ /**
36
+ * Indicates whether the field is required or not.
37
+ */
38
+ required?: boolean;
39
+
40
+ /**
41
+ * Specifies the minimum value for the field.
42
+ */
43
+ min?: number;
44
+
45
+ /**
46
+ * Specifies the maximum value for the field.
47
+ */
48
+ max?: number;
49
+
50
+ /**
51
+ * Specifies a pattern that the field value should match.
52
+ */
53
+ pattern?: 'email' | 'url' | 'hh:mm:ss' | 'phone';
54
+
55
+ /**
56
+ *Specifies whether to trim spaces.
57
+ */
58
+ trim?: boolean;
59
+ }
60
+
61
+ export interface ComponentStyles {
62
+ /**
63
+ * Specifies the top padding of the component.
64
+ */
65
+ paddingTop?: string;
66
+
67
+ /**
68
+ * Specifies the bottom padding of the component.
69
+ */
70
+ paddingBottom?: string;
71
+ }
72
+
73
+ /**
74
+ * DynamicComponent is a key feature of the component designer.
75
+ * It is a data structure used to describe dynamic components,
76
+ * which can be added to the content by the component designer and their properties can be edited on the fly.
77
+ * The output of the designer is a tree of dynamic components.
78
+ *
79
+ * For example, a text input component can be described as follows:
80
+ * ```typescript
81
+ * export interface DTextInput extends DynamicComponent {
82
+ * component: 'text-input';
83
+ * props: {
84
+ * name?: string;
85
+ * label?: string;
86
+ * placeholder?: string;
87
+ * maska?: string;
88
+ * disabled?: boolean;
89
+ * readonly?: boolean;
90
+ * rules?: ValidationRules;
91
+ * visible?: string;
92
+ * };
93
+ * }
94
+ * ```
95
+ */
96
+ export interface DynamicComponent {
97
+ /**
98
+ * The unique identifier of the component.
99
+ */
100
+ id: string;
101
+
102
+ /**
103
+ * The type of the component.
104
+ */
105
+ component: string;
106
+
107
+ /**
108
+ * The variant of the component.
109
+ * For example, a button component can have variants like 'primary', 'secondary', etc.
110
+ */
111
+ variant?: string;
112
+
113
+ /**
114
+ * The position of the component.
115
+ */
116
+ position?: string;
117
+
118
+ /**
119
+ * A flag indicating whether the component is enabled or not.
120
+ */
121
+ enabled?: boolean;
122
+
123
+ /**
124
+ * The coordinated of the component in the canvas.
125
+ */
126
+ coordinates?: Point;
127
+
128
+ /**
129
+ * The inputs of the component.
130
+ * Input ports are used to connect the component to other components.
131
+ */
132
+ inputs?: DynamicComponentIO[];
133
+
134
+ /**
135
+ * The outputs of the component.
136
+ * Output ports are used to connect the component to other components.
137
+ */
138
+ outputs?: DynamicComponentIO[];
139
+
140
+ /**
141
+ * The properties of the component.
142
+ */
143
+ props: {
144
+ /**
145
+ * The name of the component.
146
+ */
147
+ name?: string;
148
+
149
+ /**
150
+ * The visibility of the component.
151
+ */
152
+ visible?: string;
153
+
154
+ /**
155
+ * The readonly status of the component.
156
+ */
157
+ readonly?: boolean;
158
+
159
+ /**
160
+ * The child components of the component.
161
+ */
162
+ components?: DynamicComponent[];
163
+
164
+ /**
165
+ * The validation rules for the component.
166
+ */
167
+ rules?: ValidationRules;
168
+
169
+ /**
170
+ * The styles for the component.
171
+ */
172
+ styles?: ComponentStyles;
173
+
174
+ /**
175
+ * Any other properties of the component.
176
+ */
177
+ [key: string]: unknown;
178
+ };
179
+ }
180
+
181
+ /**
182
+ * DynamicComponent is a key feature of the component designer.
183
+ * It is a data structure used to describe dynamic components ,
184
+ * which can be added to the content by the component designer and their properties can be edited on the fly.
185
+ * The output of the designer is a tree of dynamic components.
186
+ *
187
+ * DynamicComponentWithPosition is a DynamicComponent with defind fractional-indexed position.
188
+ *
189
+ * For example, a text input component can be described as follows:
190
+ * ```typescript
191
+ * {
192
+ * component: 'text-input';
193
+ * props: {
194
+ * name: 'email';
195
+ * label:: string;
196
+ * placeholder?: string;
197
+ * maska?: string;
198
+ * disabled?: boolean;
199
+ * readonly?: boolean;
200
+ * rules?: ValidationRules;
201
+ * visible?: string;
202
+ * };
203
+ * }
204
+ * ```
205
+ */
206
+ export interface DynamicComponentWithPosition extends DynamicComponent {
207
+ /**
208
+ * The position of the component. This is required for DynamicComponentWithPosition.
209
+ */
210
+ position: string;
211
+
212
+ /**
213
+ * The properties of the component.
214
+ */
215
+ props: DynamicComponent['props'] & {
216
+ /**
217
+ * The child components of the component.
218
+ */
219
+ components?: DynamicComponentWithPosition[];
220
+ };
221
+ }
222
+
223
+ /**
224
+ * Defines a DynamicComponent.
225
+ * @param {T} component - The component to define.
226
+ * @returns {T} Returns the defined component.
227
+ */
228
+ export const defineDynamicComponent = <T extends DynamicComponent>(component: T): T => component;
229
+
230
+ /**
231
+ * Defines an array of DynamicComponents.
232
+ * @param {T[]} components - The components to define.
233
+ * @returns {T[]} Returns the defined components.
234
+ */
235
+ export const defineDynamicComponents = <T extends DynamicComponent>(components: T[]): T[] =>
236
+ components.map((component) => defineDynamicComponent<T>(component));
237
+
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ export * from './assertions/index.js';
2
+ export * from './data-objects/index.js';
3
+ export * from './dynamic-components/index.js';
4
+ export * from './paths/index.js';
5
+ export * from './y-components/index.js';
6
+ export * from './y-tools/index.js';
7
+
@@ -0,0 +1,164 @@
1
+ import { isObject, isString } from '@/assertions/index.js';
2
+
3
+ /**
4
+ * In the system you are using, two types of keys are used to identify elements in an array: ID keys and position keys.
5
+ * These keys provide a flexible and robust way to refer to specific elements in an array, particularly when dealing with arrays of objects.
6
+ *
7
+ * 1. **ID Keys:**
8
+ * These keys have a prefix of 'id:' and are followed by the unique identifier of an object in the array.
9
+ * For example, in your array `items: [{ id: 'xxx', name: 'John' }]`, you can refer to the object with `id: 'xxx'` using the ID key 'id:xxx'.
10
+ * This is particularly useful when you have a unique identifier for each object in your array.
11
+ * With this, even if objects move around in the array, you can always accurately refer to a specific object using its unique ID.
12
+ *
13
+ * 2. **Position Keys:**
14
+ * These keys have a prefix of 'pos:' and are followed by a position indicator.
15
+ * Position keys provide a way to refer to an object in an array based on its position rather than its content.
16
+ * This can be useful when the position of an object in an array is significant and you want to refer to an object based on where it is in the array.
17
+ *
18
+ * In your path system, you can use either type of key as part of the path to refer to specific elements in an array.
19
+ * For example, if you have an object like `{ items: [{ id: 'xxx', name: 'John' }] }`,
20
+ * you can refer to the name of the first item in the array using either an ID key or a position key in the path:
21
+ *
22
+ * - Using an ID key: `['items', 'id:xxx', 'name']`
23
+ * - Using a position key: `['items', 'pos:0', 'name']`
24
+ *
25
+ * This system of using ID and position keys in paths gives you a powerful and flexible way to refer to specific elements in complex, nested objects.
26
+ */
27
+
28
+ export const POSITION_ARRAY_KEY_PREFIX = 'pos:';
29
+
30
+ /**
31
+ * Checks if the given key is a position array key.
32
+ * @param {unknown} key - The key to check.
33
+ * @returns {boolean} Returns true if `key` is a position array key, false otherwise.
34
+ * @example
35
+ * isPositionArrayKey('pos:0'); // Returns true
36
+ * isPositionArrayKey('id:0'); // Returns false
37
+ */
38
+ export const isPositionArrayKey = (key: unknown): key is string => {
39
+ return isString(key) && key.startsWith(POSITION_ARRAY_KEY_PREFIX);
40
+ };
41
+
42
+ /**
43
+ * Creates a position array key.
44
+ * @param {string} position - The position to create the key for.
45
+ * @returns {string} Returns the created position array key.
46
+ * @example
47
+ * createPositionArrayKey('0'); // Returns 'pos:0'
48
+ */
49
+ export const createPositionArrayKey = (position: string) => {
50
+ return `${POSITION_ARRAY_KEY_PREFIX}${position}`;
51
+ };
52
+
53
+ /**
54
+ * Extracts the position from a position array key.
55
+ * @param {string} key - The position array key to extract the position from.
56
+ * @returns {string} Returns the extracted position.
57
+ * @example
58
+ * extractPositionFromArrayKey('pos:0'); // Returns '0'
59
+ */
60
+ export const extractPositionFromArrayKey = (key: string): string => {
61
+ if (!isPositionArrayKey(key)) {
62
+ throw new Error(`Invalid array key ${key}. Must be "pos:xxxxx"`);
63
+ }
64
+
65
+ return key.split(POSITION_ARRAY_KEY_PREFIX)[1];
66
+ };
67
+
68
+ /* ID KEY */
69
+ export const ID_PATH_KEY_PREFIX = 'id:';
70
+
71
+ /**
72
+ * Checks if the given key is an ID array key.
73
+ * @param {unknown} key - The key to check.
74
+ * @returns {boolean} Returns true if `key` is an ID array key, false otherwise.
75
+ * @example
76
+ * isIdArrayKey('id:123'); // Returns true
77
+ * isIdArrayKey('pos:0'); // Returns false
78
+ */
79
+ export const isIdArrayKey = (key: unknown): key is string => {
80
+ return isString(key) && key.startsWith(ID_PATH_KEY_PREFIX);
81
+ };
82
+
83
+ /**
84
+ * Creates an ID array key.
85
+ * @param {string} id - The ID to create the key for.
86
+ * @returns {string} Returns the created ID array key.
87
+ * @example
88
+ * createIdArrayKey('123'); // Returns 'id:123'
89
+ */
90
+ export const createIdArrayKey = (id: string) => {
91
+ return `${ID_PATH_KEY_PREFIX}${id}`;
92
+ };
93
+
94
+ /**
95
+ * Extracts the ID from an ID array key.
96
+ * @param {string} key - The ID array key to extract the ID from.
97
+ * @returns {string} Returns the extracted ID.
98
+ * @example
99
+ * extractIdFromArrayKey('id:123'); // Returns '123'
100
+ */
101
+ export const extractIdFromArrayKey = (key: string): string => {
102
+ if (!isIdArrayKey(key)) {
103
+ throw new Error(`Invalid array key ${key}. Must be "id:xxxxx"`);
104
+ }
105
+
106
+ return key.split(ID_PATH_KEY_PREFIX)[1];
107
+ };
108
+
109
+ /* ARRAY KEYS */
110
+
111
+ /**
112
+ * Checks if the given key is an array key (either a position array key or an ID array key).
113
+ * @param {unknown} key - The key to check.
114
+ * @returns {boolean} Returns true if `key` is an array key, false otherwise.
115
+ * @example
116
+ * isArrayKey('id:123'); // Returns true
117
+ * isArrayKey('pos:0'); // Returns true
118
+ * isArrayKey('abc'); // Returns false
119
+ */
120
+ export const isArrayKey = (key: unknown): key is string => isString(key) && (isPositionArrayKey(key) || isIdArrayKey(key));
121
+
122
+ /**
123
+ * Extracts the index from an array key in a given array.
124
+ * @param {unknown} array - The array to extract the index from.
125
+ * @param {string} key - The array key to extract the index from.
126
+ * @returns {number} Returns the extracted index.
127
+ * @example
128
+ * getIndexByArrayKey(['a', 'b', 'c'], 'pos:1'); // Returns 1
129
+ */
130
+ export const getIndexByArrayKey = (array: unknown, key: string): number => {
131
+ if (!Array.isArray(array)) {
132
+ console.error(`Non-array type`, array);
133
+
134
+ throw new Error(`Can't use array key in non array type (${key})`);
135
+ }
136
+
137
+ if (!isArrayKey(key)) {
138
+ throw new Error(`Unknown array key type (${key})`);
139
+ }
140
+
141
+ if (array.length === 0) {
142
+ return 0;
143
+ }
144
+
145
+ if (isIdArrayKey(key)) {
146
+ const id = extractIdFromArrayKey(key);
147
+ const index = array.findIndex((it) => isObject(it) && it.id === id);
148
+ if (index === -1) {
149
+ throw new Error(`Can't find index by id ${id} (${key})`);
150
+ }
151
+
152
+ return index;
153
+ } else if (isPositionArrayKey(key)) {
154
+ const position = extractPositionFromArrayKey(key);
155
+ const index = array.findIndex((it) => it.position === position);
156
+ if (index === -1) {
157
+ throw new Error(`Can't find index by position ${position} (${key})`);
158
+ }
159
+
160
+ return index;
161
+ }
162
+
163
+ return 0;
164
+ };