@jsonui/core 0.9.0 → 0.10.8

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/esm/index.js CHANGED
@@ -1,682 +1,1118 @@
1
- import {__rest}from'tslib';import {jsx}from'react/jsx-runtime';import orderBy from'lodash/orderBy';import traverse from'traverse';import jsonpointer from'jsonpointer';import cloneDeep from'lodash/cloneDeep';import findIndex from'lodash/findIndex';import pull from'lodash/pull';import Ajv from'ajv';import keyValueReplace from'key-value-replace';import {createContext}from'react';import jsonata$1 from'jsonata';import {combineReducers}from'redux';import {produce,current}from'immer';import ajvErrors from'ajv-errors';import ajvFormats from'ajv-formats';class Stock {
2
- constructor(newStock, Wrapper, reduxStore) {
3
- this.init = ({ components, functions }) => {
4
- this.stock.components = Object.assign(Object.assign({}, this.stock.components), components);
5
- this.stock.functions = Object.assign(Object.assign({}, this.stock.functions), functions);
6
- };
7
- this.registerComponent = (key, value) => {
8
- if (!!key && typeof key === 'string' && key.length > 0 && !(key in this.stock.components)) {
9
- this.stock.components[key] = value;
10
- }
11
- };
12
- this.registerFunction = (key, value) => {
13
- if (!!key && typeof key === 'string' && key.length > 0 && !(key in this.stock.functions)) {
14
- this.stock.functions[key] = value;
15
- }
16
- };
17
- this.callFunction = (name, attr, props, callerArgs) => {
18
- if (!!attr && !!name && name in this.stock.functions) {
19
- const result = this.stock.functions[name](attr, props, callerArgs, this);
20
- return result;
21
- }
22
- return null;
23
- };
24
- this.getComponent = (componentName) => !!componentName && componentName in this.stock.components
25
- ? this.stock.components[componentName]
26
- : // eslint-disable-next-line no-underscore-dangle
27
- this.stock.components._Undefined;
28
- this.stock = {
29
- components: {},
30
- functions: {},
31
- };
32
- this.Wrapper = Wrapper;
33
- this.validations = [];
34
- this.reduxStore = reduxStore;
35
- this.init(newStock);
36
- }
37
- }const SEPARATOR = '/';
38
- const STORE_ERROR_POSTFIX = '.error';
39
- const STORE_TOUCH_POSTFIX = '.touch';
40
- const PATH_MODIFIERS_KEY = '$pathModifiers';
41
- const MODIFIER_KEY = '$modifier';
1
+ import Ajv from'ajv';import addFormats from'ajv-formats';import ajvErrors from'ajv-errors';const V_COMP = '$comp';
2
+ const V_CHILDREN = '$children';
42
3
  const ACTION_KEY = '$action';
43
- const REF_ASSETS = '$assetsRef';
44
- const REF_LOCALES = '$locales';
45
- const REF_VALIDATES = '$validations';
46
- const STYLE_WEB_NAME = 'styleWeb';
47
- const STYLE_RN_NAME = 'styleRN';
48
- const REDUX_GET_FUNCTION = 'get';
49
- const REDUX_SET_FUNCTION = 'set';
50
- const REDUX_FUNCTIONS = [REDUX_GET_FUNCTION, REDUX_SET_FUNCTION];
51
- const PATHNAME = 'path';
52
- const SIMPLE_DATA_TYPES = ['string', 'number', 'boolean', 'null'];
53
- const V_CHILDREN_NAME = '$children';
54
- const V_COMP_NAME = '$comp';
4
+ const MODIFIER_KEY = '$modifier';
5
+ const PATH_MODIFIERS_KEY = '$pathModifiers';
55
6
  const LIST_SEMAPHORE = '$isList';
56
7
  const LIST_ITEM = '$listItem';
8
+ /** Main JsonUI list pagination keys (parity). */
57
9
  const LIST_PAGE = '$page';
58
10
  const LIST_ITEM_PER_PAGE = '$itemPerPage';
59
11
  const LIST_LENGTH = '$listLength';
60
- const LIST_ITEM_PER_PAGE_DEFAULT = 10;
61
- const REDUX_PATHS = '$$reduxPaths';
62
- const V_CHILDREN_PREFIX = '$child';
63
- const PARENT_PROP_NAME = '__parentprop';
64
- const CURRENT_PATH_NAME = '__currentPaths';
65
- const REDUX_GET_SUBSCRIBERS_NAME = '__subscriberPaths';
66
- const UNDEFINED_COMP_NAME = '_Undefined';
67
- const PRIMITIVE_COMP_NAME = '_PrimitiveProp'; // TODO check all literal and replace with this constant
68
- const FRAGMENT_COMP_NAME = 'Fragment'; // TODO check all literal and replace with this constant
69
- var constants=/*#__PURE__*/Object.freeze({__proto__:null,ACTION_KEY:ACTION_KEY,CURRENT_PATH_NAME:CURRENT_PATH_NAME,FRAGMENT_COMP_NAME:FRAGMENT_COMP_NAME,LIST_ITEM:LIST_ITEM,LIST_ITEM_PER_PAGE:LIST_ITEM_PER_PAGE,LIST_ITEM_PER_PAGE_DEFAULT:LIST_ITEM_PER_PAGE_DEFAULT,LIST_LENGTH:LIST_LENGTH,LIST_PAGE:LIST_PAGE,LIST_SEMAPHORE:LIST_SEMAPHORE,MODIFIER_KEY:MODIFIER_KEY,PARENT_PROP_NAME:PARENT_PROP_NAME,PATHNAME:PATHNAME,PATH_MODIFIERS_KEY:PATH_MODIFIERS_KEY,PRIMITIVE_COMP_NAME:PRIMITIVE_COMP_NAME,REDUX_FUNCTIONS:REDUX_FUNCTIONS,REDUX_GET_FUNCTION:REDUX_GET_FUNCTION,REDUX_GET_SUBSCRIBERS_NAME:REDUX_GET_SUBSCRIBERS_NAME,REDUX_PATHS:REDUX_PATHS,REDUX_SET_FUNCTION:REDUX_SET_FUNCTION,REF_ASSETS:REF_ASSETS,REF_LOCALES:REF_LOCALES,REF_VALIDATES:REF_VALIDATES,SEPARATOR:SEPARATOR,SIMPLE_DATA_TYPES:SIMPLE_DATA_TYPES,STORE_ERROR_POSTFIX:STORE_ERROR_POSTFIX,STORE_TOUCH_POSTFIX:STORE_TOUCH_POSTFIX,STYLE_RN_NAME:STYLE_RN_NAME,STYLE_WEB_NAME:STYLE_WEB_NAME,UNDEFINED_COMP_NAME:UNDEFINED_COMP_NAME,V_CHILDREN_NAME:V_CHILDREN_NAME,V_CHILDREN_PREFIX:V_CHILDREN_PREFIX,V_COMP_NAME:V_COMP_NAME});const findLastIndex = (arr, func) => {
70
- const reverseIdx = [...arr].reverse().findIndex(func);
71
- return reverseIdx === -1 ? reverseIdx : arr.length - (reverseIdx + 1);
12
+ /** Field-level inline validation spec key on a node. */
13
+ const V_VALIDATIONS = '$validations';
14
+ // Store-name suffixes for parallel trees (errors, touch-state, etc.).
15
+ const ERROR_STORE_SUFFIX = '.error';
16
+ /** Shadow store for field touched state (aligned with main JsonUI `.touch`). */
17
+ const TOUCH_STORE_SUFFIX = '.touch';
18
+ const JSON_SEPARATOR = '/';
19
+ // Single-root store layout helper.
20
+ // All logical stores live under `/storeRoot/{storeName}/...`.
21
+ const STORE_ROOT_PATH = '/storeRoot';/**
22
+ * Shared helpers for function handlers.
23
+ */
24
+ const hasAnyError$1 = (value) => {
25
+ if (value === null || value === undefined)
26
+ return false;
27
+ if (Array.isArray(value)) {
28
+ return value.some((v) => hasAnyError$1(v));
29
+ }
30
+ if (typeof value === 'object') {
31
+ return Object.values(value).some((v) => hasAnyError$1(v));
32
+ }
33
+ // Any non-null / non-undefined primitive counts as "has error".
34
+ return true;
72
35
  };
73
- const drop = (arr, n = 1) => arr.slice(n);
74
- const isNumber = (a) => typeof a === 'number';
75
- const jsonPointerFix = (path) => {
76
- if (path !== null && path !== undefined && typeof path === 'string') {
77
- let str = path;
78
- str = str.charAt(str.length - 1) === SEPARATOR ? str.slice(0, -1) : str;
79
- str = str.startsWith(SEPARATOR) ? str : `${SEPARATOR}${str}`;
80
- return str;
81
- }
82
- return SEPARATOR;
36
+ const hasAnyTouched$1 = (value) => {
37
+ if (value === true)
38
+ return true;
39
+ if (value === null || value === undefined)
40
+ return false;
41
+ if (Array.isArray(value)) {
42
+ return value.some((v) => hasAnyTouched$1(v));
43
+ }
44
+ if (typeof value === 'object') {
45
+ return Object.values(value).some((v) => hasAnyTouched$1(v));
46
+ }
47
+ return false;
83
48
  };
84
- const jsonPointerGet = (json, path) => {
85
- if (json === undefined || path === null || path === undefined || typeof path !== 'string')
86
- return undefined;
87
- if (path === SEPARATOR /* || path === '' same effect */)
88
- return json;
89
- try {
90
- return jsonpointer.get(json, jsonPointerFix(path));
49
+ /**
50
+ * Throws if value contains anything that cannot round-trip through JSON:
51
+ * undefined, function, symbol, bigint, NaN, non-finite numbers, or circular references.
52
+ */
53
+ const assertJsonCompatible = (value, seen = new WeakSet()) => {
54
+ if (value === null)
55
+ return;
56
+ if (value === undefined)
57
+ throw new Error('undefined is not JSON-compatible');
58
+ const type = typeof value;
59
+ if (type === 'string' || type === 'boolean')
60
+ return;
61
+ if (type === 'number') {
62
+ if (!Number.isFinite(value))
63
+ throw new Error(`Non-finite number is not JSON-compatible: ${value}`);
64
+ return;
91
65
  }
92
- catch (_a) {
93
- return undefined;
66
+ if (type === 'function' || type === 'symbol' || type === 'bigint') {
67
+ throw new Error(`${type} is not JSON-compatible`);
68
+ }
69
+ const obj = value;
70
+ if (seen.has(obj))
71
+ throw new Error('Circular reference is not JSON-compatible');
72
+ seen.add(obj);
73
+ if (Array.isArray(value)) {
74
+ for (const item of value)
75
+ assertJsonCompatible(item, seen);
94
76
  }
77
+ else {
78
+ for (const v of Object.values(value))
79
+ assertJsonCompatible(v, seen);
80
+ }
81
+ seen.delete(obj);
95
82
  };
96
- const jsonPointerSet = (json, path, value) => {
97
- if (json === undefined || path === null || path === undefined || typeof path !== 'string')
98
- return json;
99
- if (path === SEPARATOR || path === '') {
83
+ /**
84
+ * Deep clone helper used by get() so callers cannot mutate the internal store state.
85
+ * Only JSON-compatible values are accepted; functions and other non-JSON types throw.
86
+ */
87
+ const cloneDeep = (value) => {
88
+ if (value === null || value === undefined)
89
+ return value;
90
+ const type = typeof value;
91
+ if (type !== 'object') {
92
+ if (type === 'function' || type === 'symbol' || type === 'bigint') {
93
+ throw new Error(`${type} is not JSON-compatible`);
94
+ }
95
+ if (type === 'number' && !Number.isFinite(value)) {
96
+ throw new Error(`Non-finite number is not JSON-compatible: ${value}`);
97
+ }
100
98
  return value;
101
99
  }
102
- try {
103
- jsonpointer.set(json, jsonPointerFix(path), value);
104
- return json;
105
- // eslint-disable-next-line no-empty
100
+ if (Array.isArray(value)) {
101
+ return value.map((item) => cloneDeep(item));
102
+ }
103
+ const obj = value;
104
+ const result = {};
105
+ for (const key of Object.keys(obj)) {
106
+ result[key] = cloneDeep(obj[key]);
106
107
  }
107
- catch (_a) {
108
- return json;
108
+ return result;
109
+ };/**
110
+ * Minimal JSON Pointer implementation (RFC 6901).
111
+ * Supports get, set, and path resolution (absolute, relative, ./ ../).
112
+ *
113
+ * Paths and nesting are unbounded: e.g. /a/b/c/0/d/e/1/f is valid;
114
+ * segments can be object keys or array indices (numeric strings).
115
+ */
116
+ /**
117
+ * Normalize a path: remove empty segments and trailing/leading slashes.
118
+ * Prevents accidental keys like "" from paths such as "//" or "/a/".
119
+ * Does not decode segments (e.g. ~1 stays so one segment is preserved).
120
+ */
121
+ const normalizePath = (pathStr) => {
122
+ if (!pathStr || pathStr === '/')
123
+ return '/';
124
+ const trimmed = pathStr.startsWith('/') ? pathStr.slice(1) : pathStr;
125
+ const segments = trimmed.split(JSON_SEPARATOR).filter((s) => s !== '');
126
+ return segments.length === 0 ? '/' : JSON_SEPARATOR + segments.join(JSON_SEPARATOR);
127
+ };
128
+ const parsePath = (pathStr) => {
129
+ if (!pathStr || pathStr === '/')
130
+ return [];
131
+ if (!pathStr.startsWith(JSON_SEPARATOR)) {
132
+ throw new Error(`Invalid JSON Pointer path: ${pathStr}`);
109
133
  }
134
+ return pathStr.slice(1).split(JSON_SEPARATOR).map(decode);
110
135
  };
111
- const pathArrayToPathString = (array) => array.map((i, index) => (Number.isInteger(i) ? `[${i}]` : `${index > 0 ? '.' : ''}${i}`)).join('');
112
- const pathArrayToJsonPointer = (array) => `/${array.join('/')}`;
113
- function isOnlyObject(item) {
114
- return !!item && typeof item === 'object' && !Array.isArray(item);
115
- }
116
- const mergePath = (target, newState) => {
117
- if (!newState || typeof newState !== 'object')
118
- return target;
119
- let newTarget = cloneDeep(target);
120
- Object.entries(newState).forEach(([key, value]) => {
121
- newTarget = jsonPointerSet(newTarget, key, value);
122
- });
123
- return newTarget;
136
+ const decode = (segment) => {
137
+ for (let i = 0; i < segment.length; i++) {
138
+ if (segment[i] === '~') {
139
+ const next = segment[i + 1];
140
+ if (next !== '0' && next !== '1') {
141
+ throw new Error(`Invalid JSON Pointer escape in segment: ${segment}`);
142
+ }
143
+ i += 1;
144
+ }
145
+ }
146
+ return segment.replace(/~1/g, '/').replace(/~0/g, '~');
147
+ };
148
+ const encode = (segment) => {
149
+ return String(segment).replace(/~/g, '~0').replace(/\//g, '~1');
150
+ };
151
+ const get$1 = (obj, pathStr) => {
152
+ const segments = parsePath(pathStr); // parsePath normalizes, so /a//b/ -> /a/b
153
+ let current = obj;
154
+ for (const seg of segments) {
155
+ if (current == null || typeof current !== 'object')
156
+ return undefined;
157
+ current = current[seg];
158
+ }
159
+ return current;
160
+ };
161
+ /**
162
+ * Resolve a path against a base path (for relative paths like ./x, ../y).
163
+ * Supports arbitrary depth; excess ".." yields root then appends remaining segments.
164
+ */
165
+ const resolvePath = (basePath, relativePath) => {
166
+ if (relativePath.startsWith('/'))
167
+ return normalizePath(relativePath);
168
+ const baseSegments = parsePath(basePath);
169
+ const relSegments = relativePath.split(JSON_SEPARATOR).filter(Boolean);
170
+ for (const seg of relSegments) {
171
+ if (seg === '..') {
172
+ baseSegments.pop();
173
+ }
174
+ else if (seg !== '.') {
175
+ baseSegments.push(decode(seg));
176
+ }
177
+ }
178
+ return JSON_SEPARATOR + baseSegments.map(encode).join(JSON_SEPARATOR);
179
+ };/**
180
+ * Tree-shaped state management with multiple stores.
181
+ * Similar to Redux but tailored for JSON UI: per-store trees, JSON Pointer paths.
182
+ *
183
+ * Store paths are unbounded and can be arbitrarily nested (objects and arrays),
184
+ * e.g. /a/b/c/2/d/e/0/f. Logical paths are resolved via resolveStorePath and
185
+ * applied consistently in get/set.
186
+ */
187
+ const isTouchOrErrorShadowStore = (storeName) => {
188
+ return storeName.endsWith(TOUCH_STORE_SUFFIX) || storeName.endsWith(ERROR_STORE_SUFFIX);
124
189
  };
125
- // eslint-disable-next-line import/prefer-default-export
126
- const changeRelativePath = (path) => {
127
- let pathArray = path.split(SEPARATOR);
128
- // remove last /
129
- if (pathArray && pathArray.length > 1 && pathArray[pathArray.length - 1] === '' && !(pathArray.length === 2 && pathArray[0] === '')) {
130
- pathArray.pop();
131
- }
132
- // the last / is meaningful
133
- const absolutepathIndex = findLastIndex(pathArray, (i) => i === '');
134
- if (absolutepathIndex > 0) {
135
- pathArray = drop(pathArray, absolutepathIndex);
136
- }
137
- // all . need to remove because is just pointing the prev level
138
- pathArray = pull(pathArray, '.');
139
- let count = 0;
140
- let relativepathIndex = -1;
141
- do {
142
- count += 1;
143
- relativepathIndex = findIndex(pathArray, (i) => i === '..');
144
- if (relativepathIndex !== -1) {
145
- pathArray.splice(relativepathIndex, 1);
146
- pathArray.splice(relativepathIndex - 1, 1);
147
- }
148
- } while (relativepathIndex !== -1 && count < 100);
149
- pathArray = pull(pathArray, '..');
150
- if (pathArray.length === 1 && pathArray[0] !== '') {
151
- pathArray = ['', ...pathArray];
152
- }
153
- return pathArray.join(SEPARATOR);
190
+ class FormStore {
191
+ constructor() {
192
+ this.state = {};
193
+ this.changeListeners = new Set();
194
+ }
195
+ getState() {
196
+ return cloneDeep(this.state);
197
+ }
198
+ /**
199
+ * Logical JsonUI stores snapshot — same shape as `JsonUI` `defaultValues`:
200
+ * `{ data: {...}, "data.touch": {...}, "data.error": {...} }`.
201
+ * Omits the internal `/storeRoot` wrapper returned by {@link getState}.
202
+ */
203
+ getLogicalStoresMap() {
204
+ const slice = get$1(this.state, STORE_ROOT_PATH);
205
+ if (slice === undefined || slice === null || typeof slice !== 'object' || Array.isArray(slice)) {
206
+ return {};
207
+ }
208
+ return cloneDeep(slice);
209
+ }
210
+ /**
211
+ * Initialise a logical store root without marking fields as touched.
212
+ */
213
+ initializeStore(storeName, value) {
214
+ this.set(storeName, '/', value, false);
215
+ }
216
+ getByPointer(path) {
217
+ const value = get$1(this.state, path);
218
+ return cloneDeep(value);
219
+ }
220
+ setByPointer(path, value) {
221
+ assertJsonCompatible(value);
222
+ this.state = setImmutable(this.state, path, value);
223
+ this.notify();
224
+ }
225
+ /**
226
+ * Convenience helpers that work with logical store names and logical paths,
227
+ * instead of requiring callers to compose `/storeRoot/{storeName}/...`.
228
+ *
229
+ * Store isolation: each storeName (e.g. "data", "data.error", "data.touch") is
230
+ * a distinct store. get/set only read/write that store's subtree;
231
+ * "data.error" cannot access "data" or any other store.
232
+ *
233
+ * When trackTouch is true, set also writes to `${storeName}.touch` at
234
+ * the same logicalPath; path normalization applies (no empty segments).
235
+ */
236
+ set(storeName, logicalPath, value, trackTouch = true) {
237
+ const internalPath = makeStorePath(storeName, logicalPath);
238
+ this.setByPointer(internalPath, value);
239
+ if (trackTouch && !isTouchOrErrorShadowStore(storeName)) {
240
+ const touchStoreName = `${storeName}${TOUCH_STORE_SUFFIX}`;
241
+ this.setByPointer(makeStorePath(touchStoreName, logicalPath), true);
242
+ }
243
+ // Notify fine-grained listeners with logical store name + path so
244
+ // JsonUI can re-resolve only components that depend on this slice.
245
+ this.notifyChange(storeName, logicalPath);
246
+ }
247
+ get(storeName, logicalPath) {
248
+ const internalPath = makeStorePath(storeName, logicalPath);
249
+ return this.getByPointer(internalPath);
250
+ }
251
+ // Backward-compatible aliases for existing consumers.
252
+ setForStore(storeName, logicalPath, value, trackTouch = true) {
253
+ this.set(storeName, logicalPath, value, trackTouch);
254
+ }
255
+ // Backward-compatible aliases for existing consumers.
256
+ getForStore(storeName, logicalPath) {
257
+ return this.get(storeName, logicalPath);
258
+ }
259
+ notify() {
260
+ // No coarse-grained subscription API is exposed intentionally.
261
+ }
262
+ subscribeChange(listener) {
263
+ this.changeListeners.add(listener);
264
+ return () => this.changeListeners.delete(listener);
265
+ }
266
+ notifyChange(storeName, logicalPath) {
267
+ this.changeListeners.forEach((l) => l(storeName, logicalPath));
268
+ }
269
+ }
270
+ const makeStorePath = (storeName, path) => {
271
+ if (!storeName || storeName.length === 0) {
272
+ throw new Error('storeName must be a non-empty string');
273
+ }
274
+ const base = `${STORE_ROOT_PATH}/${storeName}`;
275
+ const pointerPath = !path || path === '/' ? '/' : path.startsWith('/') ? path : `/${path}`;
276
+ const normalized = normalizePath(pointerPath);
277
+ if (normalized === '/')
278
+ return base;
279
+ return base + normalized;
154
280
  };
155
281
  /**
156
- * Deep merge two objects.
157
- * @param target
158
- * @param ...sources
282
+ * Immutable variant of ptrSet: returns a new root object with the change applied,
283
+ * without mutating the original tree. Only the objects/arrays along the path are
284
+ * shallow-copied; unrelated subtrees are structurally shared for performance.
159
285
  */
160
- function mergeDeep(target, ...sources) {
161
- if (!sources.length)
162
- return target;
163
- const source = sources.shift();
164
- if (isOnlyObject(target) && isOnlyObject(source)) {
165
- // eslint-disable-next-line no-restricted-syntax
166
- for (const key in source) {
167
- if (isOnlyObject(source[key])) {
168
- if (!target[key])
169
- Object.assign(target, { [key]: {} });
170
- mergeDeep(target[key], source[key]);
286
+ const setImmutable = (root, pathStr, value) => {
287
+ const segments = parsePath(pathStr);
288
+ if (segments.length === 0)
289
+ return root;
290
+ const originalRoot = root;
291
+ const cloneContainer = (container) => {
292
+ if (Array.isArray(container)) {
293
+ return container.slice();
294
+ }
295
+ if (container && typeof container === 'object') {
296
+ return { ...container };
297
+ }
298
+ return {};
299
+ };
300
+ const setAt = (current, index) => {
301
+ const isLast = index === segments.length - 1;
302
+ const seg = segments[index];
303
+ const container = cloneContainer(current);
304
+ if (isLast) {
305
+ const lastSeg = seg;
306
+ const lastKey = lastSeg === '' || /^\d+$/.test(lastSeg) ? parseInt(lastSeg, 10) : lastSeg;
307
+ if (Array.isArray(container)) {
308
+ container[lastKey] = value;
171
309
  }
172
310
  else {
173
- Object.assign(target, { [key]: source[key] });
311
+ container[lastSeg] = value;
174
312
  }
313
+ return { cloned: container, result: container };
175
314
  }
315
+ const nextSeg = segments[index + 1];
316
+ const nextKey = nextSeg === '' || /^\d+$/.test(nextSeg) ? parseInt(nextSeg, 10) : nextSeg;
317
+ // Non-last segment: ensure child container exists before descending.
318
+ const keyForChild = seg;
319
+ let next = Array.isArray(container) && /^\d+$/.test(seg) ? container[parseInt(seg, 10)] : container[seg];
320
+ if (next == null ||
321
+ typeof next !== 'object' ||
322
+ (Array.isArray(next) && typeof nextKey === 'string') ||
323
+ (!Array.isArray(next) && typeof nextKey === 'number')) {
324
+ next = typeof nextKey === 'number' ? [] : {};
325
+ }
326
+ const { result: childClone } = setAt(next, index + 1);
327
+ if (Array.isArray(container) && /^\d+$/.test(String(keyForChild))) {
328
+ container[parseInt(String(keyForChild), 10)] = childClone;
329
+ }
330
+ else {
331
+ container[String(keyForChild)] = childClone;
332
+ }
333
+ return { cloned: container, result: container };
334
+ };
335
+ const { result } = setAt(originalRoot, 0);
336
+ return result;
337
+ };
338
+ /**
339
+ * Resolve a path for a given store, using pathModifiers (from list context) and currentPath for relative paths.
340
+ * - Absolute path: /firstname -> unchanged (then normalized).
341
+ * - Relative path: firstname -> resolved against currentPath (or pathModifiers[storeName] when in list context).
342
+ * - pathModifiers apply per store: only pathModifiers[storeName] is used for that store; "data.error" cannot
343
+ * access "data" or any other store.
344
+ * Returns a normalized path (no empty segments, no trailing slash).
345
+ */
346
+ const resolveStorePath = (pathStr, currentPath, pathModifiers, storeName) => {
347
+ let resolved;
348
+ const modifier = pathModifiers !== undefined && storeName !== undefined && Object.prototype.hasOwnProperty.call(pathModifiers, storeName)
349
+ ? pathModifiers[storeName]
350
+ : undefined;
351
+ if (modifier !== undefined) {
352
+ resolved = resolvePath(modifier.path, pathStr);
176
353
  }
177
- return mergeDeep(target, ...sources);
178
- }
179
- const collectObjMerge = (refConst, json) => {
180
- const res = {};
181
- if (refConst && json && typeof json === 'object') {
182
- const refs = [];
183
- // eslint-disable-next-line func-names
184
- traverse(json).forEach(function (x) {
185
- if (x && !!x[refConst] && !!this && !this.circular) {
186
- refs.push(x[refConst]);
187
- }
188
- });
189
- refs.filter((i) => !!i).forEach((i) => mergeDeep(res, i));
354
+ else if (pathStr.startsWith('/')) {
355
+ resolved = pathStr;
190
356
  }
191
- return res;
357
+ else {
358
+ resolved = resolvePath(currentPath, pathStr);
359
+ }
360
+ return normalizePath(resolved);
361
+ };const createSetAction = (formStore) => {
362
+ return async (params, ctx) => {
363
+ const storeName = params.store;
364
+ const path = params.path;
365
+ let value = params.value;
366
+ const jsonataDef = params.jsonataDef;
367
+ if (!storeName || storeName.length === 0)
368
+ return;
369
+ if (typeof jsonataDef === 'string' && jsonataDef) {
370
+ try {
371
+ const jsonata = (await import('jsonata')).default;
372
+ const expr = jsonata(jsonataDef);
373
+ value = await expr.evaluate(value);
374
+ }
375
+ catch {
376
+ value = params.value;
377
+ }
378
+ }
379
+ const logicalPath = resolveStorePath(path, ctx?.currentPath ?? '/', ctx?.pathModifiers, storeName);
380
+ formStore.set(storeName, logicalPath, value);
381
+ };
382
+ };const set = (params, ctx) => {
383
+ const setFn = createSetAction(ctx.formStore);
384
+ return setFn(params, ctx);
385
+ };const actions = {
386
+ set,
387
+ };/**
388
+ * Expands simplified node format into full JSON UI node so components receive
389
+ * value, onChange, error, etc. without the model having to specify every binding.
390
+ *
391
+ * When a node has store + path (non-empty store, string path), we add:
392
+ * - value: { $modifier: 'get', store, path }
393
+ * - onChange: { $action: 'set', store, path }
394
+ * - error / fieldErrors: { $modifier: 'get', store: store + '.error', path }
395
+ * - fieldTouched: { $modifier: 'get', store: store + '.touch', path }
396
+ *
397
+ * The component is unchanged; RenderNode uses the expanded node for prop
398
+ * resolution and the expanded props are passed through normally.
399
+ */
400
+ const isSimplifiedNode = (node) => {
401
+ if (typeof node !== 'object' || node === null)
402
+ return false;
403
+ const r = node;
404
+ const store = r.store;
405
+ const path = r.path;
406
+ return typeof store === 'string' && store.length > 0 && typeof path === 'string';
192
407
  };
193
- const collectObjToArray = (refConst, json, flatten = false) => {
194
- if (refConst && json && typeof json === 'object') {
195
- const refs = [];
196
- // eslint-disable-next-line func-names
197
- traverse(json).forEach(function (x) {
198
- if (x && !!x[refConst] && !!this && !this.circular) {
199
- refs.push(x[refConst]);
408
+ /**
409
+ * Returns an expanded node with value, onChange, error, fieldErrors, fieldTouched
410
+ * derived from store + path. Strips store and path from the result so they are
411
+ * not passed as component props.
412
+ * If the node is not simplified, returns the same node reference.
413
+ */
414
+ const expandSimplifiedNode = (node) => {
415
+ if (!isSimplifiedNode(node) && node[V_COMP] !== 'SubmitButton') {
416
+ return node;
417
+ }
418
+ const { store, path, ...rest } = node;
419
+ const expanded = {
420
+ ...(node[V_COMP] === 'SubmitButton'
421
+ ? {
422
+ onClick: {
423
+ [ACTION_KEY]: 'submit',
424
+ },
200
425
  }
201
- });
202
- return flatten === true ? refs.flat() : refs;
426
+ : {}),
427
+ ...(isSimplifiedNode(node)
428
+ ? {
429
+ value: {
430
+ [MODIFIER_KEY]: 'get',
431
+ store,
432
+ path,
433
+ },
434
+ onChange: {
435
+ [ACTION_KEY]: 'set',
436
+ store,
437
+ path,
438
+ },
439
+ fieldErrors: {
440
+ [MODIFIER_KEY]: 'get',
441
+ store,
442
+ path,
443
+ type: 'ERROR',
444
+ },
445
+ fieldTouched: {
446
+ [MODIFIER_KEY]: 'get',
447
+ store,
448
+ path,
449
+ type: 'TOUCH',
450
+ },
451
+ }
452
+ : {
453
+ //if is not a simplified node, we should add store and path props
454
+ store,
455
+ path,
456
+ }),
457
+ ...rest,
458
+ };
459
+ return expanded;
460
+ };const computeListSliceRange = ({ realDataLength, page: pageRaw, itemPerPage: itemPerPageRaw, listLength: listLengthRaw, }) => {
461
+ const coerceNonNegInt = (v, fallback) => {
462
+ if (typeof v === 'number' && Number.isInteger(v) && v >= 0)
463
+ return v;
464
+ return fallback;
465
+ };
466
+ let listLength = coerceNonNegInt(listLengthRaw, realDataLength);
467
+ if (listLengthRaw === undefined) {
468
+ listLength = realDataLength;
203
469
  }
204
- return [];
470
+ const itemPerPage = coerceNonNegInt(itemPerPageRaw, listLength);
471
+ const page = coerceNonNegInt(pageRaw, 0);
472
+ const offset = page * itemPerPage <= listLength ? page * itemPerPage : 0;
473
+ const end = Math.min(listLength, offset + itemPerPage);
474
+ const indices = [];
475
+ for (let i = offset; i < end; i++)
476
+ indices.push(i);
477
+ return { offset, end, indices };
478
+ };const getOwnPathModifiers = (node) => {
479
+ return node[PATH_MODIFIERS_KEY];
205
480
  };
206
- const isValidJson = (d) => {
207
- try {
208
- JSON.stringify(d);
481
+ const mergeEffectivePathModifiers = ({ ownPathModifiers, pathModifiers, currentPath, }) => {
482
+ if (!ownPathModifiers || typeof ownPathModifiers !== 'object') {
483
+ return pathModifiers;
484
+ }
485
+ const merged = {
486
+ ...(pathModifiers ?? {}),
487
+ };
488
+ for (const [storeName, spec] of Object.entries(ownPathModifiers)) {
489
+ if (typeof spec !== 'object')
490
+ continue;
491
+ const rawPath = spec.path;
492
+ if (typeof rawPath !== 'string' || rawPath.length === 0)
493
+ continue;
494
+ const resolved = resolveStorePath(rawPath, currentPath, pathModifiers, storeName);
495
+ merged[storeName] = { path: resolved };
209
496
  }
210
- catch (_a) {
497
+ return merged;
498
+ };/** Segment-aware path overlap check (aligned with validation.ts). */
499
+ const isPathPrefix$1 = (rulePath, targetPath) => {
500
+ const r = rulePath === '' ? '/' : rulePath;
501
+ const t = targetPath === '' ? '/' : targetPath;
502
+ if (r === '/')
503
+ return true;
504
+ if (t === r)
505
+ return true;
506
+ return t.startsWith(r.endsWith('/') ? r : `${r}/`);
507
+ };const hasAnyError = (value) => {
508
+ if (value === null || value === undefined)
211
509
  return false;
510
+ if (Array.isArray(value)) {
511
+ return value.some((v) => hasAnyError(v));
512
+ }
513
+ if (typeof value === 'object') {
514
+ return Object.values(value).some((v) => hasAnyError(v));
212
515
  }
213
516
  return true;
214
517
  };
215
- const isPrimitiveValue = (value, emptyStringAllowed = false) => value !== 'undefined' && value !== null && ['string', 'boolean', 'number', 'bigint'].includes(typeof value) && (value !== '' || emptyStringAllowed);
216
- const hasLeaf = (obj, emptyStringAllowed) => {
217
- if (typeof obj === 'object') {
218
- // eslint-disable-next-line no-restricted-syntax
219
- for (const key in obj) {
220
- // eslint-disable-next-line no-prototype-builtins
221
- if (obj.hasOwnProperty(key)) {
222
- const value = obj[key];
223
- if (typeof value === 'object') {
224
- return hasLeaf(value);
225
- }
226
- return isPrimitiveValue(value, emptyStringAllowed);
227
- }
228
- }
518
+ const hasAnyTouched = (value) => {
519
+ if (value === true)
520
+ return true;
521
+ if (value === null || value === undefined)
522
+ return false;
523
+ if (Array.isArray(value)) {
524
+ return value.some((v) => hasAnyTouched(v));
229
525
  }
230
- return isPrimitiveValue(obj, emptyStringAllowed);
231
- };var jsonUtils=/*#__PURE__*/Object.freeze({__proto__:null,changeRelativePath:changeRelativePath,collectObjMerge:collectObjMerge,collectObjToArray:collectObjToArray,drop:drop,findLastIndex:findLastIndex,hasLeaf:hasLeaf,isNumber:isNumber,isOnlyObject:isOnlyObject,isPrimitiveValue:isPrimitiveValue,isValidJson:isValidJson,jsonPointerFix:jsonPointerFix,jsonPointerGet:jsonPointerGet,jsonPointerSet:jsonPointerSet,mergeDeep:mergeDeep,mergePath:mergePath,pathArrayToJsonPointer:pathArrayToJsonPointer,pathArrayToPathString:pathArrayToPathString});const getFilteredPath = (_a, func) => {
232
- var _b = PARENT_PROP_NAME; _a[_b]; var propsNew = __rest(_a, [_b + ""]);
233
- const paths = [];
234
- function* jsonTraverse(o) {
235
- const memory = new Set();
236
- function* innerTraversal(oo, path = []) {
237
- var _a;
238
- if (memory.has(oo)) {
239
- return; // circular
526
+ if (typeof value === 'object') {
527
+ return Object.values(value).some((v) => hasAnyTouched(v));
528
+ }
529
+ return false;
530
+ };
531
+ const createGetModifier = (formStore) => {
532
+ return async (params, ctx) => {
533
+ const storeName = params.store;
534
+ const path = params.path;
535
+ const type = params.type;
536
+ const jsonataDef = params.jsonataDef;
537
+ const resolvedStoreName = type === 'ERROR' ? `${storeName}${ERROR_STORE_SUFFIX}` : type === 'TOUCH' ? `${storeName}${TOUCH_STORE_SUFFIX}` : storeName;
538
+ // Path modifiers are keyed by the logical/base store (e.g. "data"),
539
+ // not by shadow stores like "data.error".
540
+ const logicalPath = resolveStorePath(path, ctx.currentPath, ctx.pathModifiers, storeName);
541
+ let value = formStore.get(resolvedStoreName, logicalPath);
542
+ // For ERROR lookups, leaf-less containers (e.g. { players: [{}] })
543
+ // mean there is no actual validation message in the subtree.
544
+ if (type === 'ERROR' && !hasAnyError(value)) {
545
+ value = undefined;
546
+ }
547
+ // TODO need to test hasAnyTouched and hasAnyError
548
+ if (type === 'TOUCH') {
549
+ value = hasAnyTouched(value);
550
+ }
551
+ if (jsonataDef && value !== undefined) {
552
+ try {
553
+ const jsonata = (await import('jsonata')).default;
554
+ const expr = jsonata(jsonataDef);
555
+ value = await expr.evaluate(value);
240
556
  }
241
- memory.add(oo);
242
- // eslint-disable-next-line no-restricted-syntax
243
- for (const i of Object.keys(oo)) {
244
- const itemPath = path.concat(i);
245
- if (itemPath && itemPath.length > 1 && typeof oo[i] === 'object' && ((_a = oo[i]) === null || _a === void 0 ? void 0 : _a[V_COMP_NAME])) {
246
- // eslint-disable-next-line no-continue
247
- continue;
248
- }
249
- if (i === PARENT_PROP_NAME || i === CURRENT_PATH_NAME || i === LIST_ITEM) {
250
- // eslint-disable-next-line no-continue
251
- continue;
252
- }
253
- if (func({ key: i, value: oo[i], path, parent: oo, level: path.length })) {
254
- yield { key: i, value: oo[i], path, parent: oo, level: path.length };
255
- }
256
- if (oo[i] !== null && typeof oo[i] === 'object') {
257
- yield* innerTraversal(oo[i], itemPath);
258
- }
557
+ catch {
558
+ // fallback
259
559
  }
260
560
  }
261
- yield* innerTraversal(o);
561
+ return value;
562
+ };
563
+ };/**
564
+ * Resolves $modifier (and nested values) anywhere in the tree.
565
+ * Modifiers can appear at component root (value, onChange, etc.), in nested
566
+ * structures (e.g. style.fontSize, style.base.padding), or in $child* slot content.
567
+ * We recurse into objects and arrays so every occurrence is resolved.
568
+ */
569
+ //TODO add unit test to the resolveModifier
570
+ async function resolveModifier(value, modifiers, ctx) {
571
+ // If it's a direct modifier object: { $modifier: 'x', ...params }
572
+ if (value != null && typeof value === 'object' && MODIFIER_KEY in value) {
573
+ const { [MODIFIER_KEY]: mod, ...params } = value;
574
+ const resolvedParams = {};
575
+ for (const [k, v] of Object.entries(params)) {
576
+ resolvedParams[k] = await resolveModifier(v, modifiers, ctx);
577
+ }
578
+ const handler = modifiers[mod] ?? (mod === 'get' ? createGetModifier(ctx.formStore) : undefined);
579
+ if (!handler)
580
+ return undefined;
581
+ const result = handler(resolvedParams, ctx);
582
+ return result instanceof Promise ? await result : result;
583
+ }
584
+ // If it's an array, resolve modifiers in each element.
585
+ if (Array.isArray(value)) {
586
+ const resolved = await Promise.all(value.map((item) => resolveModifier(item, modifiers, ctx)));
587
+ return resolved;
262
588
  }
263
- // eslint-disable-next-line no-restricted-syntax
264
- for (const filteredvalue of jsonTraverse(propsNew)) {
265
- paths.push(filteredvalue);
589
+ // If it's a plain object without $modifier, traverse its properties
590
+ // so nested fields like www2.bbb or payload.value get resolved.
591
+ if (value != null && typeof value === 'object') {
592
+ const obj = value;
593
+ const entries = Object.entries(obj);
594
+ if (entries.length === 0)
595
+ return value;
596
+ const resolvedObj = {};
597
+ for (const [k, v] of entries) {
598
+ resolvedObj[k] = await resolveModifier(v, modifiers, ctx);
599
+ }
600
+ return resolvedObj;
266
601
  }
267
- return paths;
602
+ // Primitive or null – nothing to do.
603
+ return value;
604
+ }/**
605
+ * Style types for the shared style layer (web + React Native).
606
+ * Canonical style is a subset of CSS-like keys that we support and map to both platforms.
607
+ */
608
+ /** Default breakpoint width thresholds (min-width in px) */
609
+ const DEFAULT_BREAKPOINTS = {
610
+ base: 0,
611
+ xs: 0,
612
+ sm: 640,
613
+ md: 768,
614
+ lg: 1024,
615
+ xl: 1280,
268
616
  };
269
- const actionBuilder = (props, stock) => {
270
- const _a = props, _b = PARENT_PROP_NAME; _a[_b]; const propsNew = __rest(_a, [_b + ""]);
271
- const paths = getFilteredPath(propsNew, ({ key }) => key === ACTION_KEY);
272
- orderBy(paths, ['level'], ['desc']).forEach((i) => {
273
- const _a = traverse(props).get(i.path), _b = ACTION_KEY, functionName = _a[_b], functionParams = __rest(_a, [_b + ""]);
274
- traverse(props).set(i.path, (...callerArgs) => {
275
- stock.callFunction(functionName, functionParams, props, callerArgs);
276
- });
277
- });
617
+ /** Order for merging: base first, then xs..xl */
618
+ const BREAKPOINT_ORDER = ['base', 'xs', 'sm', 'md', 'lg', 'xl'];/**
619
+ * Resolves canonical or responsive style to platform-specific style (web or React Native).
620
+ * Pure and synchronous; no dependencies.
621
+ */
622
+ const isResponsiveStyle = (style) => {
623
+ if (typeof style !== 'object')
624
+ return false;
625
+ const keys = Object.keys(style);
626
+ return keys.some((k) => BREAKPOINT_ORDER.includes(k));
278
627
  };
279
- const calculatePropsFromModifier = (props, stock) => {
280
- const reduxPaths = [];
281
- const _a = props, _b = PARENT_PROP_NAME; _a[_b]; const propsNew = __rest(_a, [_b + ""]);
282
- const paths = getFilteredPath(propsNew, ({ key }) => key === MODIFIER_KEY);
283
- orderBy(paths, ['level'], ['desc']).forEach((i) => {
284
- const _a = traverse(props).get(i.path), _b = MODIFIER_KEY, functionName = _a[_b], functionParams = __rest(_a, [_b + ""]);
285
- if (typeof functionName === 'string' && functionName === REDUX_GET_FUNCTION) {
286
- reduxPaths.push(functionParams);
287
- }
288
- traverse(props).set(i.path, stock.callFunction(functionName, functionParams, props));
289
- });
290
- return reduxPaths;
628
+ /**
629
+ * Merge responsive style into a single style: base + all breakpoints up to and including current.
630
+ * Mobile-first: current breakpoint index determines how many layers we merge.
631
+ */
632
+ const mergeResponsive = (responsive, currentBreakpoint) => {
633
+ const idx = BREAKPOINT_ORDER.indexOf(currentBreakpoint);
634
+ const merged = {};
635
+ for (let i = 0; i <= idx; i++) {
636
+ const key = BREAKPOINT_ORDER[i];
637
+ const block = responsive[key];
638
+ if (block && typeof block === 'object') {
639
+ Object.assign(merged, block);
640
+ }
641
+ }
642
+ return merged;
291
643
  };
292
- const getCurrentPaths = (props, pathModifier) => {
293
- const currentPaths = Object.assign({}, props[CURRENT_PATH_NAME]);
294
- if (pathModifier && Object.keys(pathModifier).length !== 0) {
295
- Object.keys(pathModifier).forEach((key) => {
296
- if (!!key && !!pathModifier[key] && pathModifier[key][PATHNAME] !== undefined && pathModifier[key][PATHNAME] !== null) {
297
- const path = pathModifier[key][PATHNAME];
298
- const parent = currentPaths[key];
299
- if (`${path}`.startsWith(SEPARATOR) || !(parent && parent[PATHNAME])) {
300
- currentPaths[key] = { [PATHNAME]: path };
301
- }
302
- else {
303
- currentPaths[key] = { [PATHNAME]: changeRelativePath(`${parent[PATHNAME]}${SEPARATOR}${path}`) };
304
- }
305
- if (!!currentPaths[key] && !`${currentPaths[key][PATHNAME]}`.startsWith(SEPARATOR)) {
306
- currentPaths[key][PATHNAME] = `${SEPARATOR}${currentPaths[key][PATHNAME]}`;
307
- }
308
- }
309
- });
644
+ /**
645
+ * Parse "1px solid #ccc" or similar border shorthand into parts.
646
+ */
647
+ const parseBorder = (value) => {
648
+ const parts = value.trim().split(/\s+/);
649
+ const result = {};
650
+ for (const p of parts) {
651
+ if (/^\d+(\.\d+)?(px)?$/.test(p)) {
652
+ result.width = parseFloat(p);
653
+ }
654
+ else if (['solid', 'dashed', 'dotted', 'none'].includes(p)) {
655
+ result.style = p;
656
+ }
657
+ else if (p.startsWith('#') || p.startsWith('rgb')) {
658
+ result.color = p;
659
+ }
310
660
  }
311
- return currentPaths;
661
+ return result;
312
662
  };
313
- const normalisePrimitives = (props, parentComp) => {
314
- if (!!props && Array.isArray(props)) {
315
- return { [V_COMP_NAME]: FRAGMENT_COMP_NAME, [V_CHILDREN_NAME]: props, [PARENT_PROP_NAME]: parentComp };
663
+ /**
664
+ * Convert canonical style to React Native-compatible style.
665
+ * - border shorthand -> borderWidth, borderColor, borderStyle
666
+ * - cursor is not supported on RN, omit
667
+ * - numeric values kept as-is where RN expects numbers
668
+ */
669
+ const toNativeStyle = (canonical) => {
670
+ const out = {};
671
+ for (const [key, value] of Object.entries(canonical)) {
672
+ if (value === undefined)
673
+ continue;
674
+ if (key === 'cursor')
675
+ continue; // not supported on RN
676
+ if (key === 'border' && typeof value === 'string') {
677
+ const { width, style, color } = parseBorder(value);
678
+ if (width !== undefined)
679
+ out.borderWidth = width;
680
+ if (style)
681
+ out.borderStyle = style;
682
+ if (color)
683
+ out.borderColor = color;
684
+ continue;
685
+ }
686
+ if (key === 'borderRadius' && typeof value === 'number') {
687
+ out.borderRadius = value;
688
+ continue;
689
+ }
690
+ if ((key === 'borderTop' || key === 'borderBottom' || key === 'borderLeft' || key === 'borderRight') && typeof value === 'string') {
691
+ const { width, style, color } = parseBorder(value);
692
+ if (width !== undefined)
693
+ out[`${key}Width`] = width;
694
+ if (style)
695
+ out[`${key}Style`] = style;
696
+ if (color)
697
+ out[`${key}Color`] = color;
698
+ continue;
699
+ }
700
+ out[key] = value;
316
701
  }
317
- if (props === null || SIMPLE_DATA_TYPES.includes(typeof props)) {
318
- return { [V_COMP_NAME]: PRIMITIVE_COMP_NAME, [V_CHILDREN_NAME]: props, [PARENT_PROP_NAME]: parentComp };
702
+ return out;
703
+ };
704
+ /**
705
+ * Length-like style keys that need a unit (px) when the value is a bare number
706
+ * or numeric string (e.g. from a store/modifier like sliderValue).
707
+ */
708
+ const LENGTH_KEYS = new Set([
709
+ 'fontSize',
710
+ 'width',
711
+ 'height',
712
+ 'minWidth',
713
+ 'minHeight',
714
+ 'maxWidth',
715
+ 'maxHeight',
716
+ 'margin',
717
+ 'marginTop',
718
+ 'marginRight',
719
+ 'marginBottom',
720
+ 'marginLeft',
721
+ 'padding',
722
+ 'paddingTop',
723
+ 'paddingRight',
724
+ 'paddingBottom',
725
+ 'paddingLeft',
726
+ 'top',
727
+ 'right',
728
+ 'bottom',
729
+ 'left',
730
+ 'gap',
731
+ ]);
732
+ const ensureLengthUnit = (key, value) => {
733
+ if (value === undefined)
734
+ return value;
735
+ if (!LENGTH_KEYS.has(key))
736
+ return value;
737
+ if (typeof value === 'number')
738
+ return `${value}px`;
739
+ if (typeof value === 'string' && /^\d+(\.\d+)?$/.test(value.trim())) {
740
+ return `${value.trim()}px`;
319
741
  }
320
- return Object.assign(Object.assign({}, props), { [PARENT_PROP_NAME]: parentComp });
742
+ return value;
321
743
  };
322
- const genChildenFromListItem = (props, stock) => {
323
- let page = !!props[LIST_PAGE] && isNumber(props[LIST_PAGE]) ? props[LIST_PAGE] : 0;
324
- let listLength = !!props[LIST_LENGTH] && isNumber(props[LIST_LENGTH]) ? props[LIST_LENGTH] : undefined;
325
- let itemPerPage = !!props[LIST_ITEM_PER_PAGE] && isNumber(props[LIST_ITEM_PER_PAGE]) ? props[LIST_ITEM_PER_PAGE] : undefined;
326
- const listItem = props[LIST_ITEM];
327
- if (!listItem)
328
- return undefined;
329
- const currentPaths = props[CURRENT_PATH_NAME];
330
- if (!currentPaths)
331
- return undefined;
332
- const store = Object.keys(currentPaths)[0];
333
- if (!store)
744
+ /**
745
+ * Web: pass through with minimal changes. Normalize length props (e.g. fontSize)
746
+ * so numeric or numeric-string values get "px" and the browser applies them.
747
+ */
748
+ const toWebStyle = (canonical) => {
749
+ const out = {};
750
+ for (const [key, value] of Object.entries(canonical)) {
751
+ out[key] = ensureLengthUnit(key, value);
752
+ }
753
+ return out;
754
+ };
755
+ /**
756
+ * Resolve style input (canonical or responsive) to platform-specific style.
757
+ * - If style is responsive (has base/xs/sm/md/lg/xl), merge up to current breakpoint, then resolve.
758
+ * - If no breakpoint is provided for a responsive style, only "base" is used.
759
+ */
760
+ const resolveStyle = (style, options) => {
761
+ if (!style || typeof style !== 'object')
334
762
  return undefined;
335
- const { path } = currentPaths[store];
336
- if (currentPaths && !listLength) {
337
- if (path) {
338
- const list = stock.callFunction(REDUX_GET_FUNCTION, { store, path });
339
- listLength = !!list && Array.isArray(list) ? list.length : 0;
340
- }
763
+ let canonical;
764
+ if (isResponsiveStyle(style)) {
765
+ const breakpoint = options.breakpoint ?? 'base';
766
+ canonical = mergeResponsive(style, breakpoint);
341
767
  }
342
- if (!itemPerPage) {
343
- itemPerPage = listLength;
768
+ else {
769
+ canonical = style;
344
770
  }
345
- page = Number.isInteger(page) && page >= 0 ? page : 0;
346
- itemPerPage = !!itemPerPage && Number.isInteger(itemPerPage) && itemPerPage >= 0 ? itemPerPage : 0;
347
- listLength = !!listLength && Number.isInteger(listLength) && listLength >= 0 ? listLength : 0;
348
- const offset = page * itemPerPage <= listLength ? page * itemPerPage : 0;
349
- const children = [];
350
- // eslint-disable-next-line no-plusplus
351
- for (let i = offset; i < listLength && i < offset + itemPerPage; i++) {
352
- children.push(Object.assign(Object.assign({}, listItem), { [PATH_MODIFIERS_KEY]: {
353
- [store]: {
354
- path: `.${SEPARATOR}${i}`,
355
- },
356
- } }));
771
+ if (Object.keys(canonical).length === 0)
772
+ return undefined;
773
+ return options.platform === 'native' ? toNativeStyle(canonical) : toWebStyle(canonical);
774
+ };let inlineAjv = null;
775
+ const getInlineAjv = () => {
776
+ if (!inlineAjv) {
777
+ // TODO why it's no strict?
778
+ inlineAjv = new Ajv({ allErrors: true, strict: false });
779
+ addFormats(inlineAjv);
780
+ ajvErrors(inlineAjv);
357
781
  }
358
- return children.length > 0 ? children : undefined;
782
+ return inlineAjv;
359
783
  };
360
- const getRootWrapperProps = (props, stock) => {
361
- const subscriberPaths = calculatePropsFromModifier(props, stock);
362
- actionBuilder(props, stock);
363
- if (props[LIST_SEMAPHORE]) {
364
- // eslint-disable-next-line no-param-reassign
365
- props[V_CHILDREN_NAME] = genChildenFromListItem(props, stock);
366
- }
367
- // eslint-disable-next-line no-param-reassign
368
- props[REDUX_GET_SUBSCRIBERS_NAME] = subscriberPaths;
369
- };
370
- const isChildrenProp = (propName) => !!propName && typeof propName === 'string' && propName.startsWith(V_CHILDREN_PREFIX);
371
- const getParentProps = (props) => {
372
- return props && typeof props === 'object' && !Array.isArray(props)
373
- ? Object.keys(props)
374
- .filter((key) => !isChildrenProp(key) && key !== PARENT_PROP_NAME)
375
- .reduce((newObj, key) => {
376
- // eslint-disable-next-line no-param-reassign
377
- newObj[key] = props[key];
378
- return newObj;
379
- }, {})
380
- : {};
381
- };
382
- const getPropsChildrenFilter = ({ props, filter }) => props && typeof props === 'object' && !Array.isArray(props)
383
- ? Object.keys(props)
384
- .filter((key) => ((filter === 'withoutChildren' && !isChildrenProp(key)) || (filter === 'onlyChildren' && isChildrenProp(key))) && key !== PARENT_PROP_NAME)
385
- .reduce((newObj, key) => {
386
- // eslint-disable-next-line no-param-reassign
387
- newObj[key] = props[key];
388
- return newObj;
389
- }, {})
390
- : {};
391
- const getChildrensForRoot = (props, children, Wrapper) => {
392
- // eslint-disable-next-line no-nested-ternary
393
- if (!!props && Array.isArray(children)) {
394
- return children.map((childrenItem, index) => {
395
- // eslint-disable-next-line react/no-array-index-key
396
- return jsx(Wrapper, { props: normalisePrimitives(childrenItem, getParentProps(props)) }, index);
397
- });
784
+ const stringifyValidationError = (error) => {
785
+ if (typeof error === 'string')
786
+ return error;
787
+ if (typeof error === 'object' && error !== null && 'message' in error && typeof error.message === 'string') {
788
+ return error.message;
398
789
  }
399
- if (!!props && !!children) {
400
- return jsx(Wrapper, { props: normalisePrimitives(children, getParentProps(props)) });
790
+ try {
791
+ return String(error);
401
792
  }
402
- return undefined;
403
- };
404
- const generateChildren = (props, { Wrapper }) => props[V_COMP_NAME] !== '_PrimitiveProp' ? getChildrensForRoot(props, props[V_CHILDREN_NAME], Wrapper) : props[V_CHILDREN_NAME];
405
- const generateNewChildren = (props, { Wrapper }) => {
406
- // eslint-disable-next-line no-nested-ternary
407
- if (props) {
408
- if (Array.isArray(props)) {
409
- return props.map((childrenItem, index) => {
410
- // eslint-disable-next-line react/no-array-index-key
411
- return jsx(Wrapper, { props: normalisePrimitives(childrenItem, getParentProps(props)) }, index);
412
- });
413
- }
414
- return jsx(Wrapper, { props: normalisePrimitives(props, getParentProps(props)) });
793
+ catch {
794
+ return 'error';
415
795
  }
416
- return undefined;
417
796
  };
418
- const isTechnicalProp = (propName) => [
419
- PARENT_PROP_NAME,
420
- STYLE_WEB_NAME,
421
- V_COMP_NAME,
422
- PATH_MODIFIERS_KEY,
423
- CURRENT_PATH_NAME,
424
- PATH_MODIFIERS_KEY,
425
- LIST_SEMAPHORE,
426
- LIST_ITEM,
427
- LIST_PAGE,
428
- LIST_ITEM_PER_PAGE,
429
- LIST_LENGTH,
430
- REF_VALIDATES,
431
- 'style',
432
- REDUX_GET_SUBSCRIBERS_NAME,
433
- ].includes(propName);
434
- const removeTechnicalProps = (changeableProps) => {
435
- const _a = changeableProps, _b = PARENT_PROP_NAME; _a[_b]; const { style } = _a, _c = STYLE_WEB_NAME; _a[_c]; const _d = V_COMP_NAME; _a[_d]; const _e = PATH_MODIFIERS_KEY; _a[_e]; const _f = CURRENT_PATH_NAME; _a[_f]; const _g = REDUX_GET_SUBSCRIBERS_NAME; _a[_g]; const _h = PATH_MODIFIERS_KEY; _a[_h]; const _j = LIST_SEMAPHORE; _a[_j]; const _k = LIST_ITEM; _a[_k]; const _l = LIST_PAGE; _a[_l]; const _m = LIST_ITEM_PER_PAGE; _a[_m]; const _o = LIST_LENGTH; _a[_o]; const _p = REF_VALIDATES; _a[_p]; const newProps = __rest(_a, [_b + "", "style", _c + "", _d + "", _e + "", _f + "", _g + "", _h + "", _j + "", _k + "", _l + "", _m + "", _o + "", _p + ""]);
436
- return newProps;
437
- };var wrapperUtil=/*#__PURE__*/Object.freeze({__proto__:null,actionBuilder:actionBuilder,calculatePropsFromModifier:calculatePropsFromModifier,generateChildren:generateChildren,generateNewChildren:generateNewChildren,getChildrensForRoot:getChildrensForRoot,getCurrentPaths:getCurrentPaths,getFilteredPath:getFilteredPath,getParentProps:getParentProps,getPropsChildrenFilter:getPropsChildrenFilter,getRootWrapperProps:getRootWrapperProps,isChildrenProp:isChildrenProp,isTechnicalProp:isTechnicalProp,normalisePrimitives:normalisePrimitives,removeTechnicalProps:removeTechnicalProps});const I18nSchema = {
438
- $id: 'http://example.com/schemas/schema.json',
439
- type: 'object',
440
- additionalProperties: {
441
- type: 'object',
442
- properties: {
443
- translation: {
444
- type: 'object',
445
- additionalProperties: {
446
- type: 'string',
447
- },
448
- propertyNames: {
449
- type: 'string',
450
- },
451
- minProperties: 1,
452
- },
453
- additionalProperties: false,
454
- },
455
- },
456
- propertyNames: {
457
- pattern: '^[A-Za-z0-9_-]*$',
458
- type: 'string',
459
- },
460
- minProperties: 1,
797
+ const buildValidationRegistry = (rules) => {
798
+ const registry = {};
799
+ if (!rules || rules.length === 0)
800
+ return registry;
801
+ const ajv = new Ajv({ allErrors: true, strict: false });
802
+ // TODO: check test how looks like in jsonui
803
+ addFormats(ajv);
804
+ ajvErrors(ajv);
805
+ for (const rule of rules) {
806
+ if (rule.schema === undefined || rule.schema === null || !rule.store || !rule.path)
807
+ continue;
808
+ const validate = ajv.compile(rule.schema);
809
+ const byStore = registry[rule.store] ?? (registry[rule.store] = {});
810
+ const list = byStore[rule.path] ?? (byStore[rule.path] = []);
811
+ list.push(validate);
812
+ }
813
+ return registry;
461
814
  };
462
- class I18n {
463
- // eslint-disable-next-line consistent-this
464
- constructor({ language = 'en', resources, nonExistsHandler, keyPrefix = '{{', keyPostfix = '}}' }) {
465
- this.getLocales = () => (this.language.includes('-') ? this.language.split('-') : this.language.split('_') || [])[0];
466
- this.t = (key, options, language) => {
467
- if (!(typeof key === 'string')) {
468
- return key;
469
- }
470
- if (!this.resources || (!this.resources && !this.language && !language) || !this.resources[`${this.availableLanguageKey || language}`]) {
471
- return key;
472
- }
473
- const value = this.resources[`${this.availableLanguageKey || language}`].translation[key];
474
- if (value === undefined) {
475
- if (this.nonExistsHandler && typeof this.nonExistsHandler === 'function') {
476
- return this.nonExistsHandler(key);
477
- }
478
- return key;
479
- }
480
- if (options) {
481
- return keyValueReplace(value, options, [this.keyPrefix, this.keyPostfix]);
482
- }
483
- return value;
484
- };
485
- this.language = language;
486
- this.nonExistsHandler = nonExistsHandler;
487
- this.keyPrefix = keyPrefix;
488
- this.keyPostfix = keyPostfix;
489
- const ajv = new Ajv();
490
- const validate = ajv.compile(I18nSchema);
491
- const isValid = validate(resources);
492
- if (isValid) {
493
- this.resources = resources;
494
- }
495
- this.languages = Object.keys(resources);
496
- if (this.languages && this.languages.includes(this.language)) {
497
- this.availableLanguageKey = this.language;
498
- }
499
- else if (this.languages && this.languages.includes(this.getLocales())) {
500
- this.availableLanguageKey = this.getLocales();
501
- }
502
- }
503
- }const StockContext = createContext(null);
504
- const PathModifierContext = createContext({});// eslint-disable-next-line no-shadow
505
- var ReduxPathTypeEnum;
506
- (function (ReduxPathTypeEnum) {
507
- ReduxPathTypeEnum["ERROR"] = "ERROR";
508
- ReduxPathTypeEnum["TOUCH"] = "TOUCH";
509
- ReduxPathTypeEnum["NORMAL"] = "NORMAL";
510
- })(ReduxPathTypeEnum || (ReduxPathTypeEnum = {}));const getState = (state) => state === null || state === void 0 ? void 0 : state.root;
511
- const getValue = (state, store, path) => jsonPointerGet(state[store], path);
512
- const getStoreNameFromType = (store, type) =>
513
- // eslint-disable-next-line no-nested-ternary
514
- type === ReduxPathTypeEnum.ERROR ? `${store}${STORE_ERROR_POSTFIX}` : type === ReduxPathTypeEnum.TOUCH ? `${store}${STORE_TOUCH_POSTFIX}` : `${store}`;
515
- const getStateValue = (globalState, { store, path, type, jsonataDef }, currentPaths) => {
516
- const state = getState(globalState);
517
- if (state && store && path) {
518
- const convertedPath = currentPaths && currentPaths[store] && currentPaths[store].path ? changeRelativePath(`${currentPaths[store].path}${SEPARATOR}${path}`) : path;
519
- const storeName = getStoreNameFromType(store, type);
520
- let value = getValue(state, storeName, convertedPath);
521
- if (jsonataDef) {
522
- try {
523
- const expression = jsonata$1(jsonataDef);
524
- value = expression.evaluate(value);
525
- }
526
- catch (error) {
527
- // eslint-disable-next-line no-console
528
- console.error('jsonata error', error, jsonataDef);
815
+ /**
816
+ * Run a single inline (field-level) validation spec against the current store state.
817
+ *
818
+ * The component's own store name and resolved logical path are passed in directly —
819
+ * they come from the simplified component's `store`/`path` props, not from the spec.
820
+ *
821
+ * Supports two validation styles:
822
+ * - schema: AJV JSON Schema validation
823
+ * - jsonataDef + errorMessage (not mandatory): JSONata expression; error shown when result is
824
+ * not null, undefined, empty string, or true. errorMessage may be a plain string
825
+ * or a { $modifier: ... } expression resolved via resolveModifier.
826
+ */
827
+ const runInlineValidation = async (spec, componentStoreName, componentLogicalPath, modifiers, ctx) => {
828
+ const { formStore } = ctx;
829
+ const errorStoreName = `${componentStoreName}${ERROR_STORE_SUFFIX}`;
830
+ const value = formStore.get(componentStoreName, componentLogicalPath);
831
+ if (spec.schema != null) {
832
+ const ajv = getInlineAjv();
833
+ const validate = ajv.compile(spec.schema);
834
+ const messages = [];
835
+ const valid = validate(value);
836
+ //TODO: need to outsource validation to separate function and test it
837
+ if (!valid && validate.errors) {
838
+ for (const err of validate.errors) {
839
+ if (err.message)
840
+ messages.push(err.message);
529
841
  }
530
842
  }
531
- if (type === ReduxPathTypeEnum.ERROR) {
532
- // if we have error, need to show, the empty structure is not error, doesn't matter how deep is it
533
- return hasLeaf(value) ? value : null;
843
+ const newError = messages.length > 0 ? messages.join('; ') : null;
844
+ const currentError = formStore.get(errorStoreName, componentLogicalPath);
845
+ if ((currentError ?? null) !== newError) {
846
+ formStore.set(errorStoreName, componentLogicalPath, newError, false);
534
847
  }
535
- if (type === ReduxPathTypeEnum.TOUCH) {
536
- return hasLeaf(value); // return true if is touched.
848
+ return;
849
+ }
850
+ if (spec.jsonataDef != null) {
851
+ let result;
852
+ try {
853
+ const jsonata = (await import('jsonata')).default;
854
+ const expr = jsonata(spec.jsonataDef);
855
+ result = await expr.evaluate(value);
537
856
  }
538
- return value;
857
+ catch (e) {
858
+ result = stringifyValidationError(e);
859
+ }
860
+ const hasError = result !== null && result !== undefined && result !== '' && result !== true;
861
+ const newError = hasError ? (spec.errorMessage ? String(await resolveModifier(spec.errorMessage, modifiers, ctx)) : String(result)) : null;
862
+ const currentError = formStore.get(errorStoreName, componentLogicalPath);
863
+ if ((currentError ?? null) !== newError) {
864
+ formStore.set(errorStoreName, componentLogicalPath, newError, false);
865
+ }
866
+ return;
539
867
  }
540
- return null;
541
868
  };
542
- const compSelectorHook = (currentPaths, subscriberPaths) => (state) => {
543
- if (typeof subscriberPaths === 'object' && Array.isArray(subscriberPaths) && (subscriberPaths === null || subscriberPaths === void 0 ? void 0 : subscriberPaths.length) > 0) {
544
- return subscriberPaths.map((subscriberPath) => getStateValue(state, subscriberPath, currentPaths));
869
+ // Runs all validators whose rule path matches the changed path prefix for the given store.
870
+ // Aggregates current validation messages and writes or clears them in the matching .error store paths.
871
+ const runValidationsForPath = (registry, formStore, storeName, path) => {
872
+ const storeValidators = registry[storeName];
873
+ // No validators registered for this store at all
874
+ if (!storeValidators) {
875
+ return;
545
876
  }
546
- return undefined;
547
- };const DATA_UPDATE = 'DATA_UPDATE';
548
- const set$1 = (payload) => ({
549
- type: DATA_UPDATE,
550
- payload,
551
- });const get = (attr, { [CURRENT_PATH_NAME]: currentPaths } = {}, callerArgs, stock) => {
552
- const { store, path, type, jsonataDef } = attr;
553
- const state = stock.reduxStore.getState();
554
- return getStateValue(state, { store, path, type, jsonataDef }, currentPaths);
555
- };
556
- const set = (attr, props, callerArgs, stock) => {
557
- stock.reduxStore.dispatch(set$1(Object.assign(Object.assign({}, attr), { value: attr && attr.value !== undefined ? attr.value : callerArgs[0], [CURRENT_PATH_NAME]: props[CURRENT_PATH_NAME], stock })));
558
- };
559
- const jsonata = (_a) => {
560
- var { jsonataDef } = _a, attr = __rest(_a, ["jsonataDef"]);
561
- if (jsonataDef) {
562
- // console.log(' ---- jsonata ---- ')
563
- // console.log('jsonataDef: ', jsonataDef)
564
- // console.log('attr: ', attr)
565
- try {
566
- const expression = jsonata$1(jsonataDef);
567
- const evaluate = expression.evaluate(attr);
568
- // console.log('evaluate: ', evaluate)
569
- return evaluate;
877
+ const errorStoreName = `${storeName}${ERROR_STORE_SUFFIX}`;
878
+ // Collect new error messages per *concrete* target path (e.g. '/players/0/score').
879
+ const perPathMessages = {};
880
+ const affectedErrorPaths = new Set();
881
+ // Include any existing error leaf paths under matching rule paths so we can clear
882
+ // them if they become valid.
883
+ const collectExistingPaths = (basePath, value) => {
884
+ if (value == null)
885
+ return;
886
+ if (typeof value !== 'object') {
887
+ affectedErrorPaths.add(basePath);
888
+ return;
889
+ }
890
+ if (Array.isArray(value)) {
891
+ value.forEach((v, i) => {
892
+ collectExistingPaths(`${basePath}/${i}`, v);
893
+ });
894
+ return;
895
+ }
896
+ for (const [k, v] of Object.entries(value)) {
897
+ collectExistingPaths(basePath === '/' ? `/${k}` : `${basePath}/${k}`, v);
570
898
  }
571
- catch (error) {
572
- // eslint-disable-next-line no-console
573
- console.error('jsonata error', error, jsonataDef);
899
+ };
900
+ // Run all validators whose rule path is a prefix of the affected path.
901
+ // Example: action path '/a/b/c' should trigger validators registered for
902
+ // '/', '/a', '/a/b', and '/a/b/c'.
903
+ for (const [rulePath, validators] of Object.entries(storeValidators)) {
904
+ if (!validators)
905
+ continue;
906
+ if (!isPathPrefix(rulePath, path))
907
+ continue;
908
+ // Track existing error paths under this rule so we can clear them.
909
+ const existingSubtree = formStore.get(errorStoreName, rulePath);
910
+ if (existingSubtree !== undefined) {
911
+ collectExistingPaths(rulePath === '' ? '/' : rulePath, existingSubtree);
912
+ }
913
+ const valueAtRulePath = formStore.get(storeName, rulePath);
914
+ for (const validate of validators) {
915
+ const valid = validate(valueAtRulePath);
916
+ if (!valid && validate.errors) {
917
+ for (const err of validate.errors) {
918
+ if (!err.message)
919
+ continue;
920
+ const instancePath = err.instancePath;
921
+ // instancePath is relative to rulePath, e.g. '/0/score'
922
+ let targetPath;
923
+ if (rulePath === '' || rulePath === '/') {
924
+ targetPath = instancePath && instancePath.length > 0 ? instancePath : '/';
925
+ }
926
+ else {
927
+ targetPath = instancePath && instancePath.length > 0 ? `${rulePath}${instancePath}` : rulePath;
928
+ }
929
+ const list = perPathMessages[targetPath] ?? (perPathMessages[targetPath] = []);
930
+ list.push(err.message);
931
+ affectedErrorPaths.add(targetPath);
932
+ }
933
+ }
574
934
  }
575
935
  }
576
- return null;
936
+ // Apply updates for all affected error paths (both existing and new).
937
+ for (const targetPath of affectedErrorPaths) {
938
+ const messages = perPathMessages[targetPath] ?? [];
939
+ const newError = messages.length > 0 ? messages.join('; ') : null;
940
+ const currentError = formStore.get(errorStoreName, targetPath);
941
+ if ((currentError ?? null) === newError)
942
+ continue;
943
+ formStore.set(errorStoreName, targetPath, newError, false);
944
+ }
577
945
  };
578
- var functions = {
579
- get,
580
- set,
581
- jsonata,
582
- };const pathConverter = (path) => path.replace(/\./g, SEPARATOR);
583
- const errorConverter = (errors) => {
584
- const res = {};
585
- // convert error and add as an array item to the particular property
586
- if (errors) {
587
- errors.forEach((i) => {
588
- var _a, _b, _c, _d, _e, _f;
589
- if (i.keyword === 'required') {
590
- // because it's not parent error, need to move that level
591
- jsonpointer.set(res, `${pathConverter(`${i.instancePath}.${i.params.missingProperty}`)}/-`, i.message);
592
- }
593
- else if (i.keyword === 'errorMessage' && ((_c = (_b = (_a = i === null || i === void 0 ? void 0 : i.params) === null || _a === void 0 ? void 0 : _a.errors) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.keyword) === 'required') {
594
- jsonpointer.set(res, `${pathConverter(`${i.instancePath}.${(_f = (_e = (_d = i === null || i === void 0 ? void 0 : i.params) === null || _d === void 0 ? void 0 : _d.errors) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.params.missingProperty}`)}/-`, i.message);
946
+ /** Match whole segments only; works for paths of any depth (e.g. /a/b/0/c). */
947
+ const isPathPrefix = (rulePath, targetPath) => {
948
+ const r = rulePath === '' ? '/' : rulePath;
949
+ const t = targetPath === '' ? '/' : targetPath;
950
+ if (r === '/')
951
+ return true;
952
+ if (t === r)
953
+ return true;
954
+ return t.startsWith(r.endsWith('/') ? r : `${r}/`);
955
+ };/**
956
+ * Recursively walks a node's props value tree and collects all store path dependencies
957
+ * introduced by `$modifier: 'get'` entries. For each `get` modifier found, it resolves
958
+ * the logical store path (accounting for path modifiers and ERROR/TOUCH type variants)
959
+ * and appends it to the `deps` array. Nodes with a `$comp` key are skipped to avoid
960
+ * descending into nested component definitions.
961
+ */
962
+ const collectGetModifierDependencies = (val, currentPath, deps, effectivePathModifiers) => {
963
+ if (val && typeof val === 'object' && !Array.isArray(val) && V_COMP in val) {
964
+ return;
965
+ }
966
+ if (val && typeof val === 'object' && !Array.isArray(val) && MODIFIER_KEY in val && val[MODIFIER_KEY] === 'get') {
967
+ const storeName = val.store;
968
+ const type = val.type;
969
+ const path = val.path;
970
+ const alteredStoreName = type === 'ERROR' ? `${storeName ?? ''}${ERROR_STORE_SUFFIX}` : type === 'TOUCH' ? `${storeName ?? ''}${TOUCH_STORE_SUFFIX}` : storeName;
971
+ if (storeName && alteredStoreName) {
972
+ if (typeof path === 'string' && path) {
973
+ const logicalPath = resolveStorePath(path, currentPath, effectivePathModifiers, storeName);
974
+ deps.push({ store: alteredStoreName, path: logicalPath });
595
975
  }
596
976
  else {
597
- jsonpointer.set(res, `${pathConverter(i.instancePath)}/-`, i.message);
977
+ deps.push({ store: alteredStoreName, path: '/' });
598
978
  }
599
- });
979
+ }
980
+ return;
981
+ }
982
+ if (Array.isArray(val)) {
983
+ val.forEach((v) => collectGetModifierDependencies(v, currentPath, deps, effectivePathModifiers));
984
+ return;
985
+ }
986
+ if (val && typeof val === 'object') {
987
+ for (const v of Object.values(val)) {
988
+ collectGetModifierDependencies(v, currentPath, deps, effectivePathModifiers);
989
+ }
990
+ }
991
+ };const runValidationSpecsFromNode = async (node, modifiers, ctx, componentStoreName, componentLogicalPath) => {
992
+ const rawValidation = node[V_VALIDATIONS];
993
+ if (!rawValidation || !Array.isArray(rawValidation) || rawValidation.length === 0)
994
+ return;
995
+ if (!componentStoreName || componentLogicalPath == null)
996
+ return;
997
+ for (const item of rawValidation) {
998
+ if (item === null || typeof item !== 'object')
999
+ continue;
1000
+ await runInlineValidation(item, componentStoreName, componentLogicalPath, modifiers, ctx);
600
1001
  }
601
- // TODO: if it's a root level validation, looks like : {-: 'should be email'} should be converted
602
- return res && res['-'] ? res['-'] : res;
603
- };
604
- const validateJSON = (schema, data) => {
605
- const ajv = new Ajv({ allErrors: true });
606
- ajvErrors(ajv);
607
- ajvFormats(ajv);
608
- const validate = ajv.compile(schema);
609
- const valid = validate(data);
610
- return {
611
- valid: validate(data),
612
- error: valid ? null : errorConverter(validate.errors),
613
- };
614
1002
  };
615
- const validateJSONAndStore = (schema, store, data) => {
616
- const { valid, error } = validateJSON(schema, data);
617
- return {
618
- store: `${store}${STORE_ERROR_POSTFIX}`,
619
- valid,
620
- value: error,
621
- };
622
- };const initialState = {};
623
- const globalValidateNewState = (stock, newState, actionStore, actionPath) => {
624
- if (stock === null || stock === void 0 ? void 0 : stock.validations) {
625
- stock.validations.forEach((validateItem) => {
626
- if (validateItem.store === actionStore && `${actionPath}`.startsWith(validateItem.path)) {
627
- if (validateItem.schema) {
628
- const state = current(newState);
629
- const stateToBeValidated = jsonPointerGet(state, `${SEPARATOR}${actionStore}${validateItem.path}`);
630
- const errors = validateJSONAndStore(validateItem.schema, actionStore, stateToBeValidated);
631
- // eslint-disable-next-line no-param-reassign
632
- newState = jsonPointerSet(newState, `${SEPARATOR}${errors.store}${validateItem.path}`, errors.value);
633
- }
1003
+ const runRenderNodeResolution = async ({ node, modifiers, ctx, currentPath, effectivePathModifiers, stylePlatform, styleBreakpoint, componentStore, componentPath, }) => {
1004
+ const props = {};
1005
+ const deps = [];
1006
+ const resolvedSlots = {};
1007
+ for (const [key, value] of Object.entries(node)) {
1008
+ if (key.startsWith('$child') || !key.startsWith('$')) {
1009
+ collectGetModifierDependencies(value, currentPath, deps, effectivePathModifiers);
1010
+ if (key.startsWith('$child')) {
1011
+ resolvedSlots[key] = await resolveModifier(value, modifiers, ctx);
1012
+ }
1013
+ else if (!key.startsWith('$')) {
1014
+ props[key] = await resolveModifier(value, modifiers, ctx);
634
1015
  }
1016
+ }
1017
+ }
1018
+ if (props.style != null && typeof props.style === 'object') {
1019
+ const resolved = resolveStyle(props.style, {
1020
+ platform: stylePlatform,
1021
+ breakpoint: styleBreakpoint,
635
1022
  });
1023
+ props.style = resolved ?? props.style;
636
1024
  }
637
- };
638
- const reducer = (state = initialState, action) => {
639
- switch (action === null || action === void 0 ? void 0 : action.type) {
640
- case DATA_UPDATE: {
641
- const { store = undefined, path = undefined, value = undefined, jsonataDef = undefined, [CURRENT_PATH_NAME]: currentPaths = undefined, stock = undefined, } = (action === null || action === void 0 ? void 0 : action.payload) || {};
642
- if (store && path && isValidJson(value)) {
643
- const storekey = `${store}`;
644
- let convertedPath = currentPaths && currentPaths[storekey] && currentPaths[storekey].path
645
- ? changeRelativePath(`${currentPaths[storekey].path}${SEPARATOR}${path}`)
646
- : changeRelativePath(path);
647
- convertedPath = jsonPointerFix(convertedPath);
648
- const absolutePathWithStoreKey = `${SEPARATOR}${storekey}${convertedPath}`;
649
- const newState = produce(state, (draft) => {
650
- if (jsonataDef) {
651
- try {
652
- const expression = jsonata$1(jsonataDef);
653
- const newValue = expression.evaluate(value);
654
- // eslint-disable-next-line no-param-reassign
655
- draft = jsonPointerSet(draft, absolutePathWithStoreKey, newValue);
656
- }
657
- catch (error) {
658
- // eslint-disable-next-line no-console
659
- console.error('jsonata error', error, jsonataDef);
660
- // eslint-disable-next-line no-param-reassign
661
- draft = jsonPointerSet(draft, absolutePathWithStoreKey, value);
662
- }
663
- }
664
- else {
665
- // eslint-disable-next-line no-param-reassign
666
- draft = jsonPointerSet(draft, absolutePathWithStoreKey, value);
667
- }
668
- // set, if a leaf touched
669
- // TODO if array or object touched, can easily overwite the leaf touched
670
- // eslint-disable-next-line no-param-reassign
671
- draft = jsonPointerSet(draft, `${SEPARATOR}${storekey}${STORE_TOUCH_POSTFIX}${convertedPath}`, true);
672
- // if validatior has match, need to validate it synchronously
673
- globalValidateNewState(stock, draft, store, convertedPath);
674
- });
675
- return newState;
676
- }
677
- return state;
1025
+ const resolvedComponentPath = componentStore && componentPath != null ? resolveStorePath(componentPath, currentPath, effectivePathModifiers, componentStore) : undefined;
1026
+ await runValidationSpecsFromNode(node, modifiers, ctx, componentStore, resolvedComponentPath);
1027
+ return {
1028
+ state: { props, resolvedSlots },
1029
+ deps,
1030
+ };
1031
+ };const resolveAction = (value, actions, modifiers, ctx) => {
1032
+ if (value != null && typeof value === 'object' && ACTION_KEY in value) {
1033
+ const { [ACTION_KEY]: actionName, ...params } = value;
1034
+ const hasExplicitValue = Object.prototype.hasOwnProperty.call(value, 'value');
1035
+ // Extract modifiers from action ctx.
1036
+ const { componentProps, validators, ...modifierCtx } = ctx;
1037
+ let handler = actions[actionName];
1038
+ if (!handler && actionName === 'set') {
1039
+ handler = createSetAction(ctx.formStore);
678
1040
  }
679
- default:
680
- return state;
1041
+ if (!handler)
1042
+ return undefined;
1043
+ return async (e) => {
1044
+ // TODO: research, why componentProps need
1045
+ const resolvedParams = { ...componentProps, ...params };
1046
+ // Case 1: value from event (input onChange) – only when the model
1047
+ // did NOT define a value explicitly.
1048
+ if (!hasExplicitValue && e != null && typeof e === 'object' && 'target' in e) {
1049
+ const target = e.target;
1050
+ if (target?.value !== undefined)
1051
+ resolvedParams.value = target.value;
1052
+ }
1053
+ // Cases 2 & 3: static JSON value or nested $modifier value – both
1054
+ // flow through resolveModifier below and are preserved.
1055
+ for (const [k, v] of Object.entries(resolvedParams)) {
1056
+ resolvedParams[k] = await resolveModifier(v, modifiers, modifierCtx);
1057
+ }
1058
+ const result = handler(resolvedParams, ctx);
1059
+ if (result instanceof Promise)
1060
+ await result;
1061
+ // Run validations for this store/path if configured
1062
+ const storeName = resolvedParams.store;
1063
+ const rawPath = resolvedParams.path;
1064
+ if (validators && storeName && rawPath) {
1065
+ // Resolve to logical path so validations work with lists, pathModifiers,
1066
+ // and relative paths (e.g. "score" inside /players/0).
1067
+ const logicalPath = resolveStorePath(rawPath, ctx.currentPath, ctx.pathModifiers, storeName);
1068
+ runValidationsForPath(validators, ctx.formStore, storeName, logicalPath);
1069
+ }
1070
+ };
681
1071
  }
682
- };const rootReducer = combineReducers({ root: reducer });export{I18n,PathModifierContext,Stock,StockContext,compSelectorHook,constants,functions as stockFunctions,rootReducer as storeReducers,jsonUtils as utils,wrapperUtil};//# sourceMappingURL=index.js.map
1072
+ return undefined;
1073
+ };const get = (params, ctx) => {
1074
+ const getFn = createGetModifier(ctx.formStore);
1075
+ return getFn(params, ctx);
1076
+ };async function jsonata(params) {
1077
+ const { jsonataDef, ...input } = params;
1078
+ if (typeof jsonataDef !== 'string' || !jsonataDef)
1079
+ return undefined;
1080
+ try {
1081
+ const jsonata = (await import('jsonata')).default;
1082
+ const expr = jsonata(jsonataDef);
1083
+ // Evaluate with the remaining params (e.g. { error, touched }) as input.
1084
+ return await expr.evaluate(input);
1085
+ }
1086
+ catch {
1087
+ // On any jsonata error, fall back to undefined so UI doesn't break.
1088
+ return undefined;
1089
+ }
1090
+ }const isNotTouchedOrHasError = (params) => {
1091
+ const { error, touched } = params;
1092
+ const hasError = hasAnyError$1(error);
1093
+ const isTouched = hasAnyTouched$1(touched);
1094
+ return !isTouched || hasError;
1095
+ };/**
1096
+ * Translation function: looks up key in ctx.translations using active language.
1097
+ */
1098
+ const t = (params, ctx) => {
1099
+ const key = params.key;
1100
+ if (!key)
1101
+ return undefined;
1102
+ const translations = ctx.translations ?? {};
1103
+ const langParam = params.lang;
1104
+ const baseLang = ctx.defaultLanguage || 'en';
1105
+ const activeLang = langParam || ctx.activeLanguage || baseLang;
1106
+ const entry = translations[key];
1107
+ // If we're in the base language, or there is no translation table
1108
+ // for this key, just return the key as-is (baseline text).
1109
+ if (activeLang === baseLang || entry === undefined)
1110
+ return key;
1111
+ // Otherwise, use the requested language if available, falling back to key.
1112
+ return entry[activeLang] ?? key;
1113
+ };const modifiers = {
1114
+ get,
1115
+ jsonata,
1116
+ isNotTouchedOrHasError,
1117
+ t,
1118
+ };export{ACTION_KEY,BREAKPOINT_ORDER,DEFAULT_BREAKPOINTS,ERROR_STORE_SUFFIX,FormStore,LIST_ITEM,LIST_ITEM_PER_PAGE,LIST_LENGTH,LIST_PAGE,LIST_SEMAPHORE,MODIFIER_KEY,PATH_MODIFIERS_KEY,TOUCH_STORE_SUFFIX,V_CHILDREN,V_COMP,V_VALIDATIONS,actions,buildValidationRegistry,computeListSliceRange,expandSimplifiedNode,getOwnPathModifiers,isPathPrefix$1 as isPathPrefix,mergeEffectivePathModifiers,modifiers,normalizePath,resolveAction,resolveStorePath,runRenderNodeResolution};//# sourceMappingURL=index.js.map