@regle/core 1.11.0-beta.1 → 1.11.0-beta.2

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.
@@ -1,7 +1,6 @@
1
1
  import { computed, effectScope, getCurrentInstance, getCurrentScope, isRef, markRaw, nextTick, onMounted, onScopeDispose, reactive, ref, shallowRef, toRef, toValue, triggerRef, unref, version, watch, watchEffect } from "vue";
2
2
  import { setupDevtoolsPlugin } from "@vue/devtools-api";
3
3
 
4
- //#region ../shared/utils/isFile.ts
5
4
  /**
6
5
  * Server side friendly way of checking for a File
7
6
  */
@@ -9,8 +8,6 @@ function isFile(value) {
9
8
  return value?.constructor?.name == "File" || value?.constructor?.name == "FileList";
10
9
  }
11
10
 
12
- //#endregion
13
- //#region ../shared/utils/isEmpty.ts
14
11
  /**
15
12
  * This is the inverse of isFilled. It will check if the value is in any way empty (including arrays and objects)
16
13
  *
@@ -30,12 +27,8 @@ function isEmpty(value, considerEmptyArrayInvalid = true) {
30
27
  return !String(value).length;
31
28
  }
32
29
 
33
- //#endregion
34
- //#region ../shared/utils/symbol.ts
35
30
  const RegleRuleSymbol = Symbol("regle-rule");
36
31
 
37
- //#endregion
38
- //#region ../shared/utils/cloneDeep.ts
39
32
  function getRegExpFlags(regExp) {
40
33
  if (typeof regExp.source.flags == "string") return regExp.source.flags;
41
34
  else {
@@ -62,8 +55,6 @@ function cloneDeep(obj) {
62
55
  return result;
63
56
  }
64
57
 
65
- //#endregion
66
- //#region ../shared/utils/object.utils.ts
67
58
  function isObject(obj) {
68
59
  if (obj && (obj instanceof Date || obj.constructor.name == "File" || obj.constructor.name == "FileList")) return false;
69
60
  return typeof obj === "object" && obj !== null && !Array.isArray(obj);
@@ -146,8 +137,6 @@ function dotPathObjectToNested(obj) {
146
137
  return result;
147
138
  }
148
139
 
149
- //#endregion
150
- //#region ../shared/utils/toDate.ts
151
140
  /**
152
141
  * This utility will coerce any string, number or Date value into a Date using the Date constructor.
153
142
  */
@@ -160,8 +149,6 @@ function toDate(argument) {
160
149
  else return /* @__PURE__ */ new Date(NaN);
161
150
  }
162
151
 
163
- //#endregion
164
- //#region ../shared/utils/debounce.ts
165
152
  function debounce(func, wait, { immediate = false, trackDebounceRef } = {}) {
166
153
  let timeout;
167
154
  const debouncedFn = (...args) => {
@@ -202,8 +189,6 @@ function debounce(func, wait, { immediate = false, trackDebounceRef } = {}) {
202
189
  return debouncedFn;
203
190
  }
204
191
 
205
- //#endregion
206
- //#region ../shared/utils/isEqual.ts
207
192
  function isEqual(a, b, deep = false, firstDeep = true) {
208
193
  if (a === b) return true;
209
194
  if (a && b && typeof a == "object" && typeof b == "object") {
@@ -236,8 +221,6 @@ function isEqual(a, b, deep = false, firstDeep = true) {
236
221
  return a !== a && b !== b;
237
222
  }
238
223
 
239
- //#endregion
240
- //#region ../shared/utils/abortablePromise.ts
241
224
  var AbortError = class extends Error {
242
225
  constructor(message = "Promise was aborted") {
243
226
  super(message);
@@ -279,15 +262,11 @@ function abortablePromise(input) {
279
262
  };
280
263
  }
281
264
 
282
- //#endregion
283
- //#region src/types/rules/rule.internal.types.ts
284
265
  const InternalRuleType = {
285
266
  Inline: "__inline",
286
267
  Async: "__async"
287
268
  };
288
269
 
289
- //#endregion
290
- //#region src/types/utils/groups.ts
291
270
  function mergeBooleanGroupProperties(entries, property) {
292
271
  return entries.some((entry) => {
293
272
  if (!property) return false;
@@ -302,8 +281,6 @@ function mergeArrayGroupProperties(entries, property) {
302
281
  }, []);
303
282
  }
304
283
 
305
- //#endregion
306
- //#region src/core/createRule/unwrapRuleParameters.ts
307
284
  /**
308
285
  * Returns a clean list of parameters
309
286
  * Removing Ref and executing function to return the unwrapped value
@@ -335,8 +312,6 @@ function getFunctionParametersLength(func) {
335
312
  return (paramsMatch[0] || paramsMatch[1] || paramsMatch[2] || paramsMatch[3] || paramsMatch[4] || "").split(",").map((p) => p.trim()).filter((p) => p.length > 0).length;
336
313
  }
337
314
 
338
- //#endregion
339
- //#region src/core/createRule/defineRuleProcessors.ts
340
315
  function defineRuleProcessors(definition, ...params) {
341
316
  const { validator, type, async } = definition;
342
317
  const isAsync = async || type === InternalRuleType.Async || validator.constructor.name === "AsyncFunction";
@@ -393,8 +368,6 @@ function defineRuleProcessors(definition, ...params) {
393
368
  });
394
369
  }
395
370
 
396
- //#endregion
397
- //#region src/core/createRule/createRule.ts
398
371
  /**
399
372
  * Create a typed custom rule that can be used like default rules.
400
373
  * It can also be declared in the global options
@@ -458,8 +431,98 @@ function createRule(definition) {
458
431
  throw new Error("[createRule] validator must be a function");
459
432
  }
460
433
 
461
- //#endregion
462
- //#region src/core/useStorage/useStorage.ts
434
+ /**
435
+ * Checks if a Vue Ref is an object.
436
+ *
437
+ * @param obj - The Ref to check
438
+ * @returns True if the Ref is an object, false otherwise
439
+ */
440
+ function isRefObject(obj) {
441
+ return isObject(obj.value);
442
+ }
443
+ /**
444
+ * Unwraps a collection ($each) getter function or returns the getter directly if it's not a function.
445
+ *
446
+ * @template T - The type of the getter function or the getter value
447
+ * @param getter - The getter function or value to unwrap
448
+ * @param value - The value to pass to the getter
449
+ * @param index - The index to pass to the getter
450
+ * @returns An object containing the scope and the unwrapped value
451
+ */
452
+ function unwrapGetter(getter, value, index) {
453
+ const scope = effectScope();
454
+ let unwrapped;
455
+ if (getter instanceof Function) unwrapped = scope.run(() => getter(value, index ?? 0));
456
+ else unwrapped = getter;
457
+ return {
458
+ scope,
459
+ unwrapped
460
+ };
461
+ }
462
+
463
+ const VersionIs = {
464
+ LessThan: -1,
465
+ EqualTo: 0,
466
+ GreaterThan: 1
467
+ };
468
+ /**
469
+ * Compare two versions quickly.
470
+ * @param current Is this version greater, equal to, or less than the other?
471
+ * @param other The version to compare against the current version
472
+ * @return 1 if current is greater than other, 0 if they are equal or equivalent, and -1 if current is less than other
473
+ */
474
+ function versionCompare(current, other) {
475
+ const cp = String(current).split(".");
476
+ const op = String(other).split(".");
477
+ for (let depth = 0; depth < Math.min(cp.length, op.length); depth++) {
478
+ const cn = Number(cp[depth]);
479
+ const on = Number(op[depth]);
480
+ if (cn > on) return VersionIs.GreaterThan;
481
+ if (on > cn) return VersionIs.LessThan;
482
+ if (!isNaN(cn) && isNaN(on)) return VersionIs.GreaterThan;
483
+ if (isNaN(cn) && !isNaN(on)) return VersionIs.LessThan;
484
+ }
485
+ return VersionIs.EqualTo;
486
+ }
487
+ const isVueSuperiorOrEqualTo3dotFive = versionCompare(version, "3.5.0") === -1 ? false : true;
488
+
489
+ function uniqueIDNuxt() {
490
+ return Math.floor(Math.random() * Date.now()).toString();
491
+ }
492
+ /**
493
+ * Generates a random SSR compatible ID.
494
+ */
495
+ function randomId() {
496
+ if (typeof window === "undefined") return uniqueIDNuxt();
497
+ else return window.crypto.getRandomValues(new Uint32Array(1))[0].toString(10);
498
+ }
499
+
500
+ function tryOnScopeDispose(fn) {
501
+ if (getCurrentScope()) {
502
+ onScopeDispose(fn);
503
+ return true;
504
+ }
505
+ return false;
506
+ }
507
+ /**
508
+ * Creates a global state that is shared for scoped validation.
509
+ *
510
+ * @param stateFactory - The function that creates the state
511
+ * @returns The state factory function
512
+ */
513
+ function createGlobalState(stateFactory) {
514
+ let initialized = false;
515
+ let state;
516
+ const scope = effectScope(true);
517
+ return ((...args) => {
518
+ if (!initialized) {
519
+ state = scope.run(() => stateFactory(...args));
520
+ initialized = true;
521
+ }
522
+ return state;
523
+ });
524
+ }
525
+
463
526
  /**
464
527
  * Inspired by Vuelidate storage
465
528
  */
@@ -580,108 +643,6 @@ function useStorage() {
580
643
  };
581
644
  }
582
645
 
583
- //#endregion
584
- //#region src/utils/object.utils.ts
585
- /**
586
- * Checks if a Vue Ref is an object.
587
- *
588
- * @param obj - The Ref to check
589
- * @returns True if the Ref is an object, false otherwise
590
- */
591
- function isRefObject(obj) {
592
- return isObject(obj.value);
593
- }
594
- /**
595
- * Unwraps a collection ($each) getter function or returns the getter directly if it's not a function.
596
- *
597
- * @template T - The type of the getter function or the getter value
598
- * @param getter - The getter function or value to unwrap
599
- * @param value - The value to pass to the getter
600
- * @param index - The index to pass to the getter
601
- * @returns An object containing the scope and the unwrapped value
602
- */
603
- function unwrapGetter(getter, value, index) {
604
- const scope = effectScope();
605
- let unwrapped;
606
- if (getter instanceof Function) unwrapped = scope.run(() => getter(value, index ?? 0));
607
- else unwrapped = getter;
608
- return {
609
- scope,
610
- unwrapped
611
- };
612
- }
613
-
614
- //#endregion
615
- //#region src/utils/version-compare.ts
616
- const VersionIs = {
617
- LessThan: -1,
618
- EqualTo: 0,
619
- GreaterThan: 1
620
- };
621
- /**
622
- * Compare two versions quickly.
623
- * @param current Is this version greater, equal to, or less than the other?
624
- * @param other The version to compare against the current version
625
- * @return 1 if current is greater than other, 0 if they are equal or equivalent, and -1 if current is less than other
626
- */
627
- function versionCompare(current, other) {
628
- const cp = String(current).split(".");
629
- const op = String(other).split(".");
630
- for (let depth = 0; depth < Math.min(cp.length, op.length); depth++) {
631
- const cn = Number(cp[depth]);
632
- const on = Number(op[depth]);
633
- if (cn > on) return VersionIs.GreaterThan;
634
- if (on > cn) return VersionIs.LessThan;
635
- if (!isNaN(cn) && isNaN(on)) return VersionIs.GreaterThan;
636
- if (isNaN(cn) && !isNaN(on)) return VersionIs.LessThan;
637
- }
638
- return VersionIs.EqualTo;
639
- }
640
- const isVueSuperiorOrEqualTo3dotFive = versionCompare(version, "3.5.0") === -1 ? false : true;
641
-
642
- //#endregion
643
- //#region src/utils/randomId.ts
644
- function uniqueIDNuxt() {
645
- return Math.floor(Math.random() * Date.now()).toString();
646
- }
647
- /**
648
- * Generates a random SSR compatible ID.
649
- */
650
- function randomId() {
651
- if (typeof window === "undefined") return uniqueIDNuxt();
652
- else return window.crypto.getRandomValues(new Uint32Array(1))[0].toString(10);
653
- }
654
-
655
- //#endregion
656
- //#region src/utils/state.utils.ts
657
- function tryOnScopeDispose(fn) {
658
- if (getCurrentScope()) {
659
- onScopeDispose(fn);
660
- return true;
661
- }
662
- return false;
663
- }
664
- /**
665
- * Creates a global state that is shared for scoped validation.
666
- *
667
- * @param stateFactory - The function that creates the state
668
- * @returns The state factory function
669
- */
670
- function createGlobalState(stateFactory) {
671
- let initialized = false;
672
- let state;
673
- const scope = effectScope(true);
674
- return ((...args) => {
675
- if (!initialized) {
676
- state = scope.run(() => stateFactory(...args));
677
- initialized = true;
678
- }
679
- return state;
680
- });
681
- }
682
-
683
- //#endregion
684
- //#region src/core/useRegle/guards/ruleDef.guards.ts
685
646
  function isNestedRulesDef(state, rules) {
686
647
  return isRefObject(state) || isObject(rules.value) && !isEmpty(rules.value) && !Object.entries(rules.value).some(([_, rule]) => isRuleDef(rule) || typeof rule === "function");
687
648
  }
@@ -702,8 +663,6 @@ function isFormRuleDefinition(rule) {
702
663
  return true;
703
664
  }
704
665
 
705
- //#endregion
706
- //#region src/core/useRegle/guards/rule.status.guards.ts
707
666
  function isNestedRulesStatus(rule) {
708
667
  return isObject(rule) && "$fields" in rule;
709
668
  }
@@ -714,8 +673,6 @@ function isFieldStatus(rule) {
714
673
  return !!rule && "$rules" in rule;
715
674
  }
716
675
 
717
- //#endregion
718
- //#region src/core/useRegle/useErrors.ts
719
676
  function extractRulesIssues({ field, silent = false }) {
720
677
  const ruleIssues = Object.entries(field.$rules ?? {}).map(([key, rule]) => {
721
678
  let message = "";
@@ -794,8 +751,6 @@ function iterateErrors(errors, includePath = false, _path) {
794
751
  } else return Object.entries(errors).map(([key, value]) => iterateErrors(value, includePath, path?.concat(key))).flat();
795
752
  }
796
753
 
797
- //#endregion
798
- //#region src/core/useRegle/root/createReactiveRuleStatus.ts
799
754
  function createReactiveRuleStatus({ customMessages, rule, ruleKey, state, path, cachePath, storage, modifiers }) {
800
755
  let scope = effectScope();
801
756
  let scopeState = {};
@@ -970,8 +925,6 @@ function createReactiveRuleStatus({ customMessages, rule, ruleKey, state, path,
970
925
  });
971
926
  }
972
927
 
973
- //#endregion
974
- //#region src/core/useRegle/root/standard-schemas.ts
975
928
  function createStandardSchema(validateFn) {
976
929
  return { "~standard": {
977
930
  version: 1,
@@ -987,8 +940,6 @@ function createStandardSchema(validateFn) {
987
940
  } };
988
941
  }
989
942
 
990
- //#endregion
991
- //#region src/core/useRegle/root/createReactiveFieldStatus.ts
992
943
  function createReactiveFieldStatus({ state, rulesDef, customMessages, path, cachePath, fieldName, storage, options, externalErrors, schemaErrors, schemaMode, onUnwatch, $isArray, initialState, originalState, shortcuts, onValidate }) {
993
944
  let scope = effectScope();
994
945
  let scopeState;
@@ -1418,8 +1369,6 @@ function createReactiveFieldStatus({ state, rulesDef, customMessages, path, cach
1418
1369
  });
1419
1370
  }
1420
1371
 
1421
- //#endregion
1422
- //#region src/core/useRegle/root/collections/createReactiveCollectionElement.ts
1423
1372
  function createCollectionElement({ $id, path, cachePath, index, options, storage, stateValue, customMessages, rules, externalErrors, schemaErrors, initialState, originalState, shortcuts, fieldName, schemaMode }) {
1424
1373
  const $fieldId = stateValue.value?.$id ?? rules.$key ?? randomId();
1425
1374
  let $cachePath = `${cachePath}.${String($fieldId)}`;
@@ -1458,8 +1407,6 @@ function createCollectionElement({ $id, path, cachePath, index, options, storage
1458
1407
  return $status;
1459
1408
  }
1460
1409
 
1461
- //#endregion
1462
- //#region src/core/useRegle/root/collections/createReactiveCollectionRoot.ts
1463
1410
  function createReactiveCollectionStatus({ state, rulesDef, customMessages, path, storage, options, externalErrors, schemaErrors, schemaMode, initialState, originalState, shortcuts, fieldName }) {
1464
1411
  let scope = effectScope();
1465
1412
  let scopeState;
@@ -1866,8 +1813,6 @@ function createReactiveCollectionStatus({ state, rulesDef, customMessages, path,
1866
1813
  });
1867
1814
  }
1868
1815
 
1869
- //#endregion
1870
- //#region src/core/useRegle/root/createReactiveNestedStatus.ts
1871
1816
  function createReactiveNestedStatus({ rulesDef, state, path = "", cachePath, rootRules, externalErrors, schemaErrors, rootSchemaErrors, validationGroups, initialState, originalState, fieldName,...commonArgs }) {
1872
1817
  let scope = effectScope();
1873
1818
  let scopeState;
@@ -2110,6 +2055,16 @@ function createReactiveNestedStatus({ rulesDef, state, path = "", cachePath, roo
2110
2055
  return false;
2111
2056
  });
2112
2057
  const $name = computed(() => fieldName);
2058
+ const $modifiers = computed(() => {
2059
+ return {
2060
+ autoDirty: $autoDirty.value,
2061
+ lazy: unref(commonArgs.options.lazy) ?? false,
2062
+ rewardEarly: $rewardEarly.value,
2063
+ silent: $silent.value,
2064
+ clearExternalErrorsOnChange: unref(commonArgs.options.clearExternalErrorsOnChange) ?? false,
2065
+ id: unref(commonArgs.options.id)
2066
+ };
2067
+ });
2113
2068
  function processShortcuts() {
2114
2069
  if (commonArgs.shortcuts?.nested) Object.entries(commonArgs.shortcuts.nested).forEach(([key, value]) => {
2115
2070
  const scope$1 = effectScope();
@@ -2135,7 +2090,8 @@ function createReactiveNestedStatus({ rulesDef, state, path = "", cachePath, roo
2135
2090
  $fields,
2136
2091
  $edited,
2137
2092
  $anyEdited,
2138
- $issues
2093
+ $issues,
2094
+ "~modifiers": unref(commonArgs.options)
2139
2095
  }));
2140
2096
  });
2141
2097
  return result;
@@ -2184,7 +2140,8 @@ function createReactiveNestedStatus({ rulesDef, state, path = "", cachePath, roo
2184
2140
  $localPending,
2185
2141
  $autoDirty,
2186
2142
  $silent,
2187
- $value
2143
+ $value,
2144
+ $modifiers
2188
2145
  };
2189
2146
  });
2190
2147
  }
@@ -2291,6 +2248,7 @@ function createReactiveNestedStatus({ rulesDef, state, path = "", cachePath, roo
2291
2248
  $clearExternalErrors,
2292
2249
  $extractDirtyFields,
2293
2250
  $abort,
2251
+ ...!!rootRules ? { "~modifiers": scopeState.$modifiers } : {},
2294
2252
  ...createStandardSchema($validate)
2295
2253
  });
2296
2254
  watchEffect(() => {
@@ -2335,171 +2293,132 @@ function createReactiveChildrenStatus({ rulesDef,...properties }) {
2335
2293
  });
2336
2294
  }
2337
2295
 
2338
- //#endregion
2339
- //#region src/core/useRegle/root/useRootStorage.ts
2340
- function useRootStorage({ initialState, originalState, options, scopeRules, state, customRules, shortcuts, schemaErrors, schemaMode = false, onValidate }) {
2341
- const storage = useStorage();
2342
- const regle = ref();
2343
- const computedExternalErrors = ref();
2344
- let $unwatchExternalErrors;
2345
- let $unwatchComputedExternalErrors;
2346
- function defineExternalErrorsWatchSource() {
2347
- $unwatchExternalErrors = watch(() => options.externalErrors?.value, () => {
2348
- $unwatchComputedExternalErrors?.();
2349
- if (options.externalErrors?.value && Object.keys(options.externalErrors.value).some((key) => key.includes("."))) computedExternalErrors.value = dotPathObjectToNested(options.externalErrors.value);
2350
- else computedExternalErrors.value = options.externalErrors?.value;
2351
- defineComputedExternalErrorsWatchSource();
2352
- }, {
2353
- immediate: true,
2354
- deep: true
2355
- });
2356
- }
2357
- function defineComputedExternalErrorsWatchSource() {
2358
- $unwatchComputedExternalErrors = watch(() => computedExternalErrors.value, () => {
2359
- $unwatchExternalErrors?.();
2360
- if (options.externalErrors?.value) options.externalErrors.value = computedExternalErrors.value;
2361
- defineExternalErrorsWatchSource();
2362
- }, { deep: true });
2296
+ const COLORS = {
2297
+ ERROR: {
2298
+ text: 16777215,
2299
+ bg: 15680580
2300
+ },
2301
+ INVALID: {
2302
+ text: 16777215,
2303
+ bg: 16747520
2304
+ },
2305
+ VALID: {
2306
+ text: 16777215,
2307
+ bg: 1096065
2308
+ },
2309
+ PENDING: {
2310
+ text: 16777215,
2311
+ bg: 16096779
2312
+ },
2313
+ DIRTY: {
2314
+ text: 2042167,
2315
+ bg: 16708551
2316
+ },
2317
+ COMPONENT: {
2318
+ text: 16777215,
2319
+ bg: 6514417
2320
+ },
2321
+ PRISTINE: {
2322
+ text: 1120295,
2323
+ bg: 16777215
2324
+ },
2325
+ INACTIVE: {
2326
+ text: 0,
2327
+ bg: 11581118
2363
2328
  }
2364
- defineExternalErrorsWatchSource();
2365
- if (isNestedRulesDef(state, scopeRules)) regle.value = createReactiveNestedStatus({
2366
- rootRules: scopeRules,
2367
- rulesDef: scopeRules,
2368
- state,
2369
- customMessages: customRules?.(),
2370
- storage,
2371
- options,
2372
- externalErrors: computedExternalErrors,
2373
- validationGroups: options.validationGroups,
2374
- initialState,
2375
- originalState,
2376
- shortcuts,
2377
- fieldName: "root",
2378
- path: "",
2379
- cachePath: "",
2380
- schemaErrors,
2381
- rootSchemaErrors: schemaErrors,
2382
- schemaMode,
2383
- onValidate
2384
- });
2385
- else if (isValidatorRulesDef(scopeRules)) regle.value = createReactiveFieldStatus({
2386
- rulesDef: scopeRules,
2387
- state,
2388
- customMessages: customRules?.(),
2389
- storage,
2390
- options,
2391
- externalErrors: computedExternalErrors,
2392
- initialState,
2393
- originalState,
2394
- shortcuts,
2395
- fieldName: "root",
2396
- path: "",
2397
- cachePath: "",
2398
- schemaMode,
2399
- schemaErrors,
2400
- onValidate
2401
- });
2402
- if (getCurrentScope()) onScopeDispose(() => {
2403
- regle.value?.$unwatch();
2404
- $unwatchComputedExternalErrors?.();
2405
- $unwatchExternalErrors?.();
2406
- });
2407
- return reactive({ regle });
2408
- }
2409
-
2410
- //#endregion
2411
- //#region src/core/useRegle/shared.rootRegle.ts
2412
- function createRootRegleLogic({ state, rulesFactory, options, globalOptions, customRules, shortcuts }) {
2413
- const definedRules = isRef(rulesFactory) ? rulesFactory : typeof rulesFactory === "function" ? void 0 : computed(() => rulesFactory);
2414
- const resolvedOptions = {
2415
- ...globalOptions,
2416
- ...options
2417
- };
2418
- let unwatchRules;
2419
- const watchableRulesGetters = shallowRef(definedRules ?? {});
2420
- if (typeof rulesFactory === "function") unwatchRules = watchEffect(() => {
2421
- watchableRulesGetters.value = rulesFactory(state);
2422
- triggerRef(watchableRulesGetters);
2423
- });
2424
- const initialState = ref(isObject(state.value) ? { ...cloneDeep(state.value) } : cloneDeep(state.value));
2425
- const originalState = isObject(state.value) ? { ...cloneDeep(state.value) } : cloneDeep(state.value);
2426
- tryOnScopeDispose(() => {
2427
- unwatchRules?.();
2428
- });
2429
- const regle = useRootStorage({
2430
- scopeRules: watchableRulesGetters,
2431
- state,
2432
- options: resolvedOptions,
2433
- initialState,
2434
- originalState,
2435
- customRules,
2436
- shortcuts
2437
- });
2438
- if (process.env.NODE_ENV === "development" && regle.regle) registerRegleInstance(regle.regle, { name: toValue(resolvedOptions.id) });
2439
- return regle;
2440
- }
2329
+ };
2330
+ const PRIORITY_KEYS = {
2331
+ ROOT: [
2332
+ "$invalid",
2333
+ "$dirty",
2334
+ "$error",
2335
+ "$pending",
2336
+ "$valid",
2337
+ "$ready"
2338
+ ],
2339
+ FIELD: [
2340
+ "$value",
2341
+ "$invalid",
2342
+ "$dirty",
2343
+ "$error",
2344
+ "$pending",
2345
+ "$errors"
2346
+ ]
2347
+ };
2348
+ const INSPECTOR_IDS = {
2349
+ INSPECTOR: "regle-inspector",
2350
+ TIMELINE: "regle-timeline"
2351
+ };
2441
2352
 
2442
- //#endregion
2443
- //#region src/devtools/registry.ts
2444
- var RegleDevtoolsRegistry = class {
2445
- instances = reactive(/* @__PURE__ */ new Map());
2446
- watchers = /* @__PURE__ */ new Map();
2447
- idCounter = 0;
2448
- notifyCallbacks = /* @__PURE__ */ new Set();
2449
- register(r$, options) {
2450
- const id = `regle-${++this.idCounter}`;
2451
- const name = options?.name || `Regle #${this.idCounter}`;
2452
- this.instances.set(id, {
2353
+ function useRegleDevtoolsRegistry() {
2354
+ const instances = shallowRef(/* @__PURE__ */ new Map());
2355
+ const watchers = shallowRef(/* @__PURE__ */ new Map());
2356
+ let idCounter = 0;
2357
+ const notifyCallbacks = shallowRef(/* @__PURE__ */ new Set());
2358
+ function register(r$, options) {
2359
+ const id = `${options?.uid?.toString() ?? "regle"}#${++idCounter}`;
2360
+ const name = options?.name || `Regle #${idCounter}`;
2361
+ instances.value.set(id, {
2453
2362
  id,
2454
2363
  name,
2455
2364
  r$,
2456
2365
  componentName: options?.componentName ? `<${options.componentName}>` : void 0
2457
2366
  });
2458
- this.notifyDevtools();
2367
+ notifyDevtools();
2459
2368
  return id;
2460
2369
  }
2461
- unregister(id) {
2462
- this.instances.delete(id);
2463
- const watcher = this.watchers.get(id);
2370
+ function unregister(id) {
2371
+ instances.value.delete(id);
2372
+ const watcher = watchers.value.get(id);
2464
2373
  if (watcher) {
2465
2374
  watcher();
2466
- this.watchers.delete(id);
2375
+ watchers.value.delete(id);
2467
2376
  }
2468
- this.notifyDevtools();
2377
+ notifyDevtools();
2469
2378
  }
2470
- getAll() {
2471
- return Array.from(this.instances.values());
2379
+ function getAll() {
2380
+ return Array.from(instances.value.values());
2472
2381
  }
2473
- get(id) {
2474
- return this.instances.get(id);
2382
+ function get(id) {
2383
+ return instances.value.get(id);
2475
2384
  }
2476
- clear() {
2477
- this.watchers.forEach((stop) => stop());
2478
- this.watchers.clear();
2479
- this.instances.clear();
2480
- this.notifyDevtools();
2385
+ function clear() {
2386
+ watchers.value.forEach((stop) => stop());
2387
+ watchers.value.clear();
2388
+ instances.value.clear();
2389
+ notifyDevtools();
2481
2390
  }
2482
- onInstancesChange(callback) {
2483
- this.notifyCallbacks.add(callback);
2391
+ function onInstancesChange(callback) {
2392
+ notifyCallbacks.value.add(callback);
2484
2393
  return () => {
2485
- this.notifyCallbacks.delete(callback);
2394
+ notifyCallbacks.value.delete(callback);
2486
2395
  };
2487
2396
  }
2488
- addWatcher(id, stopHandle) {
2489
- this.watchers.set(id, stopHandle);
2397
+ function addWatcher(id, stopHandle) {
2398
+ watchers.value.set(id, stopHandle);
2490
2399
  }
2491
- notifyDevtools() {
2492
- this.notifyCallbacks.forEach((callback) => callback());
2400
+ function notifyDevtools() {
2401
+ notifyCallbacks.value.forEach((callback) => callback());
2493
2402
  }
2494
- };
2495
- const regleDevtoolsRegistry = new RegleDevtoolsRegistry();
2403
+ return {
2404
+ register,
2405
+ unregister,
2406
+ getAll,
2407
+ get,
2408
+ clear,
2409
+ onInstancesChange,
2410
+ addWatcher
2411
+ };
2412
+ }
2413
+ const regleDevtoolsRegistry = useRegleDevtoolsRegistry();
2496
2414
  function registerRegleInstance(r$, options) {
2497
2415
  if (typeof window === "undefined") return;
2498
2416
  const instance = getCurrentInstance();
2499
2417
  const componentName = instance?.type?.name || instance?.type?.__name;
2500
2418
  const id = regleDevtoolsRegistry.register(r$, {
2501
2419
  name: options?.name,
2502
- componentName
2420
+ componentName,
2421
+ uid: instance?.uid
2503
2422
  });
2504
2423
  tryOnScopeDispose(() => {
2505
2424
  regleDevtoolsRegistry.unregister(id);
@@ -2514,1069 +2433,1108 @@ function watchRegleInstance(id, r$, onChange) {
2514
2433
  return stopHandle;
2515
2434
  }
2516
2435
 
2517
- //#endregion
2518
- //#region src/core/useRegle/useRegle.ts
2519
- function createUseRegleComposable(customRules, options, shortcuts) {
2520
- const globalOptions = {
2521
- autoDirty: options?.autoDirty,
2522
- lazy: options?.lazy,
2523
- rewardEarly: options?.rewardEarly,
2524
- silent: options?.silent,
2525
- clearExternalErrorsOnChange: options?.clearExternalErrorsOnChange
2526
- };
2527
- function useRegle$1(state, rulesFactory, options$1) {
2528
- return { r$: createRootRegleLogic({
2529
- state: isRef(state) ? state : ref(state),
2530
- rulesFactory,
2531
- options: options$1,
2532
- globalOptions,
2533
- customRules,
2534
- shortcuts
2535
- }).regle };
2536
- }
2537
- return useRegle$1;
2436
+ function isMethod(value) {
2437
+ return typeof value === "function";
2538
2438
  }
2539
- /**
2540
- * useRegle serves as the foundation for validation logic.
2541
- *
2542
- * It accepts the following inputs:
2543
- *
2544
- * @param state - This can be a plain object, a ref, a reactive object, or a structure containing nested refs.
2545
- * @param rules - These should align with the structure of your state.
2546
- * @param modifiers - Customize regle behaviour
2547
- *
2548
- * ```ts
2549
- * import { useRegle } from '@regle/core';
2550
- import { required } from '@regle/rules';
2551
-
2552
- const { r$ } = useRegle({ email: '' }, {
2553
- email: { required }
2554
- })
2555
- * ```
2556
- * Docs: {@link https://reglejs.dev/core-concepts/}
2557
- */
2558
- const useRegle = createUseRegleComposable();
2559
-
2560
- //#endregion
2561
- //#region src/core/useRegle/inferRules.ts
2562
- function createInferRuleHelper() {
2563
- function inferRules$1(state, rulesFactory) {
2564
- return rulesFactory;
2565
- }
2566
- return inferRules$1;
2439
+ function getPriorityProperties(obj, keys) {
2440
+ return keys.filter((key) => key in obj && !isMethod(obj[key])).map((key) => {
2441
+ let editable = false;
2442
+ if (key === "$value") editable = true;
2443
+ return {
2444
+ key,
2445
+ value: obj[key],
2446
+ editable
2447
+ };
2448
+ });
2567
2449
  }
2568
- /**
2569
- * Rule type helper to provide autocomplete and typecheck to your form rules or part of your form rules
2570
- * It will just return the rules without any processing.
2571
- *
2572
- * @param state - The state reference
2573
- * @param rules - Your rule tree
2574
- */
2575
- const inferRules = createInferRuleHelper();
2576
-
2577
- //#endregion
2578
- //#region src/core/useRegle/useRules.ts
2579
- function registerRegleWithDevtools(regle, options) {
2580
- try {
2581
- registerRegleInstance(regle, { name: options?.devtoolsName });
2582
- } catch (e) {}
2450
+ function getRemainingProperties(obj, excludeKeys) {
2451
+ return Object.entries(obj).filter(([key]) => !excludeKeys.includes(key) && key.startsWith("$") && !isMethod(obj[key])).map(([key, value]) => ({
2452
+ key,
2453
+ value,
2454
+ editable: false
2455
+ }));
2583
2456
  }
2584
- function createEmptyRuleState(rules) {
2585
- const result = {};
2586
- if (Object.entries(rules).some(([_, rule]) => isRuleDef(rule) || typeof rule === "function")) return null;
2587
- for (const key in rules) {
2588
- const item = rules[key];
2589
- if (!!item && isObject(item) && "$each" in item && item["$each"] && isObject(item["$each"])) result[key] = [createEmptyRuleState(item["$each"])];
2590
- else if (isObject(item) && !isEmpty(item) && !Object.entries(item).some(([_, rule]) => isRuleDef(rule) || typeof rule === "function")) result[key] = createEmptyRuleState(item);
2591
- else result[key] = null;
2592
- }
2593
- return result;
2457
+ function parseFieldNodeId(nodeId) {
2458
+ if (!nodeId.includes(":field:")) return null;
2459
+ const [instanceId, , fieldName] = nodeId.split(":");
2460
+ return {
2461
+ instanceId,
2462
+ fieldName
2463
+ };
2594
2464
  }
2595
- function createUseRulesComposable(customRules, options, shortcuts) {
2596
- const globalOptions = {
2597
- autoDirty: options?.autoDirty,
2598
- lazy: options?.lazy,
2599
- rewardEarly: options?.rewardEarly,
2600
- silent: options?.silent,
2601
- clearExternalErrorsOnChange: options?.clearExternalErrorsOnChange
2465
+ function createFieldNodeId(instanceId, fieldName) {
2466
+ return `${instanceId}:field:${fieldName}`;
2467
+ }
2468
+ function parseRuleNodeId(nodeId) {
2469
+ if (!nodeId.includes(":rule:")) return null;
2470
+ const parts = nodeId.split(":rule:");
2471
+ if (parts.length !== 2) return null;
2472
+ const fieldPart = parts[0];
2473
+ const ruleName = parts[1];
2474
+ const fieldInfo = parseFieldNodeId(fieldPart);
2475
+ if (!fieldInfo) return null;
2476
+ return {
2477
+ ...fieldInfo,
2478
+ ruleName
2602
2479
  };
2603
- function useRules$1(rulesFactory, options$1) {
2604
- const definedRules = isRef(rulesFactory) ? rulesFactory : typeof rulesFactory === "function" ? void 0 : computed(() => rulesFactory);
2605
- const regle = createRootRegleLogic({
2606
- state: ref(createEmptyRuleState(definedRules?.value)),
2607
- rulesFactory: definedRules,
2608
- options: options$1,
2609
- globalOptions,
2610
- customRules,
2611
- shortcuts
2612
- });
2613
- registerRegleWithDevtools(regle.regle, options$1);
2614
- return regle.regle;
2615
- }
2616
- return useRules$1;
2617
2480
  }
2618
- /**
2619
- * useRules is a clone of useRegle, without the need to provide a state.
2620
- *
2621
- * It accepts the following inputs:
2622
- *
2623
- * @param rules - Your rules object
2624
- * @param modifiers - Customize regle behaviour
2625
- *
2626
- * ```ts
2627
- * import { useRules } from '@regle/core';
2628
- import { required } from '@regle/rules';
2481
+ function createRuleNodeId(instanceId, fieldName, ruleName) {
2482
+ return `${createFieldNodeId(instanceId, fieldName)}:rule:${ruleName}`;
2483
+ }
2629
2484
 
2630
- const { r$ } = useRules({
2631
- email: { required }
2632
- })
2633
- * ```
2485
+ /**
2486
+ * Build state for a field node
2634
2487
  */
2635
- const useRules = createUseRulesComposable();
2636
-
2637
- //#endregion
2638
- //#region src/core/defineRegleConfig.ts
2488
+ function buildFieldState(fieldStatus) {
2489
+ const state = {};
2490
+ const priorityProperties = getPriorityProperties(fieldStatus, PRIORITY_KEYS.FIELD);
2491
+ if (priorityProperties.length > 0) state["State"] = priorityProperties;
2492
+ const remainingProperties = getRemainingProperties(fieldStatus, [
2493
+ ...PRIORITY_KEYS.FIELD,
2494
+ "$rules",
2495
+ "$fields"
2496
+ ]);
2497
+ if (remainingProperties.length > 0) state["Other Properties"] = remainingProperties;
2498
+ return state;
2499
+ }
2639
2500
  /**
2640
- * Define a global regle configuration, where you can:
2641
- * - Customize built-in rules messages
2642
- * - Add your custom rules
2643
- * - Define global modifiers
2644
- * - Define shortcuts
2645
- *
2646
- * It will return:
2647
- *
2648
- * - a `useRegle` composable that can typecheck your custom rules
2649
- * - an `inferRules` helper that can typecheck your custom rules
2501
+ * Build state for a rule node
2650
2502
  */
2651
- function defineRegleConfig({ rules, modifiers, shortcuts }) {
2652
- const useRegle$1 = createUseRegleComposable(rules, modifiers, shortcuts);
2653
- const useRules$1 = createUseRulesComposable(rules, modifiers, shortcuts);
2654
- useRegle$1.__config = {
2655
- rules,
2656
- modifiers,
2657
- shortcuts
2658
- };
2659
- useRules$1.__config = {
2660
- rules,
2661
- modifiers,
2662
- shortcuts
2663
- };
2664
- return {
2665
- useRegle: useRegle$1,
2666
- inferRules: createInferRuleHelper(),
2667
- useRules: useRules$1
2668
- };
2503
+ function buildRuleState(ruleStatus) {
2504
+ const state = {};
2505
+ const ruleKeys = [
2506
+ "$type",
2507
+ "$valid",
2508
+ "$active",
2509
+ "$pending",
2510
+ "$message",
2511
+ "$tooltip"
2512
+ ];
2513
+ const priorityProperties = getPriorityProperties(ruleStatus, ruleKeys);
2514
+ if (priorityProperties.length > 0) state["Rule State"] = priorityProperties;
2515
+ if (ruleStatus.$params && Array.isArray(ruleStatus.$params) && ruleStatus.$params.length > 0) state["Parameters"] = [{
2516
+ key: "$params",
2517
+ value: ruleStatus.$params,
2518
+ editable: false
2519
+ }];
2520
+ if (ruleStatus.$metadata !== void 0 && ruleStatus.$metadata !== true && ruleStatus.$metadata !== false) state["Metadata"] = [{
2521
+ key: "$metadata",
2522
+ value: ruleStatus.$metadata,
2523
+ objectType: "reactive",
2524
+ editable: false
2525
+ }];
2526
+ const remainingProperties = getRemainingProperties(ruleStatus, [
2527
+ ...ruleKeys,
2528
+ "$params",
2529
+ "$metadata",
2530
+ "$haveAsync",
2531
+ "$validating",
2532
+ "$fieldDirty",
2533
+ "$fieldInvalid",
2534
+ "$fieldPending",
2535
+ "$fieldCorrect",
2536
+ "$fieldError",
2537
+ "$maybePending",
2538
+ "$externalErrors"
2539
+ ]);
2540
+ if (remainingProperties.length > 0) state["Other Properties"] = remainingProperties;
2541
+ return state;
2669
2542
  }
2670
- /**
2671
- * Extend an already created custom `useRegle` (as the first parameter)
2672
- *
2673
- * It will return:
2674
- *
2675
- * - a `useRegle` composable that can typecheck your custom rules
2676
- * - an `inferRules` helper that can typecheck your custom rules
2677
- */
2678
- function extendRegleConfig(regle, { rules, modifiers, shortcuts }) {
2679
- const rootConfig = regle.__config ?? {};
2680
- const newRules = () => ({
2681
- ...rootConfig.rules?.(),
2682
- ...rules?.()
2683
- });
2684
- const newModifiers = rootConfig.modifiers && modifiers ? merge(rootConfig.modifiers, modifiers) : rootConfig.modifiers ?? modifiers;
2685
- const newShortcuts = rootConfig.shortcuts && shortcuts ? merge(rootConfig.shortcuts, shortcuts) : rootConfig.shortcuts ?? shortcuts;
2686
- const useRegle$1 = createUseRegleComposable(newRules, newModifiers, newShortcuts);
2687
- useRegle$1.__config = {
2688
- rules: newRules,
2689
- modifiers: newModifiers,
2690
- shortcuts: newShortcuts
2691
- };
2692
- return {
2693
- useRegle: useRegle$1,
2694
- inferRules: createInferRuleHelper()
2695
- };
2543
+ function buildRootState(r$) {
2544
+ const state = {};
2545
+ const priorityProperties = getPriorityProperties(r$, PRIORITY_KEYS.ROOT);
2546
+ if (priorityProperties.length > 0) state["State"] = priorityProperties;
2547
+ const remainingProperties = getRemainingProperties(r$, [...PRIORITY_KEYS.ROOT, "$fields"]);
2548
+ if (remainingProperties.length > 0) state["Other Properties"] = remainingProperties;
2549
+ if (r$["~modifiers"]) state["Modifiers"] = Object.entries(r$["~modifiers"]).map(([key, value]) => ({
2550
+ key,
2551
+ value,
2552
+ editable: false
2553
+ }));
2554
+ return state;
2555
+ }
2556
+ function resolveFieldByPath(fields, path) {
2557
+ if (!fields || !path) return null;
2558
+ const segments = path.match(/[^.\[\]]+/g);
2559
+ if (!segments) return null;
2560
+ let current = fields;
2561
+ for (const segment of segments) {
2562
+ if (!current) return null;
2563
+ const index = parseInt(segment);
2564
+ if (!isNaN(index)) if (isCollectionRulesStatus(current) && Array.isArray(current.$each)) current = current.$each[index];
2565
+ else return null;
2566
+ else if (isNestedRulesStatus(current) && current.$fields && current.$fields[segment]) current = current.$fields[segment];
2567
+ else if (current[segment]) current = current[segment];
2568
+ else return null;
2569
+ }
2570
+ return current;
2571
+ }
2572
+ function buildInspectorState(nodeId, getInstance) {
2573
+ const ruleInfo = parseRuleNodeId(nodeId);
2574
+ if (ruleInfo) {
2575
+ const { instanceId, fieldName, ruleName } = ruleInfo;
2576
+ const instance$1 = getInstance(instanceId);
2577
+ if (!instance$1 || !instance$1.r$.$fields) return null;
2578
+ const fieldStatus = resolveFieldByPath(instance$1.r$.$fields, fieldName);
2579
+ if (!fieldStatus || !("$rules" in fieldStatus)) return null;
2580
+ const ruleStatus = fieldStatus.$rules[ruleName];
2581
+ if (!ruleStatus) return null;
2582
+ return buildRuleState(ruleStatus);
2583
+ }
2584
+ const fieldInfo = parseFieldNodeId(nodeId);
2585
+ if (fieldInfo) {
2586
+ const { instanceId, fieldName } = fieldInfo;
2587
+ const instance$1 = getInstance(instanceId);
2588
+ if (!instance$1 || !instance$1.r$.$fields) return null;
2589
+ const fieldStatus = resolveFieldByPath(instance$1.r$.$fields, fieldName);
2590
+ if (!fieldStatus) return null;
2591
+ return buildFieldState(fieldStatus);
2592
+ }
2593
+ const instance = getInstance(nodeId);
2594
+ if (!instance) return null;
2595
+ return buildRootState(instance.r$);
2696
2596
  }
2697
2597
 
2698
- //#endregion
2699
- //#region src/core/mergeRegles.ts
2700
- function mergeRegles(regles, _scoped) {
2701
- const scoped = _scoped == null ? false : _scoped;
2702
- const $value = computed({
2703
- get: () => {
2704
- if (scoped) return Object.values(regles).map((r) => r.$value);
2705
- else return Object.fromEntries(Object.entries(regles).map(([key, r]) => [key, r.$value]));
2706
- },
2707
- set: (value) => {
2708
- if (!scoped) {
2709
- if (typeof value === "object") Object.entries(value).forEach(([key, newValue]) => regles[key].$value = newValue);
2710
- }
2598
+ function handleValidateAction(nodeId, api) {
2599
+ if (nodeId.includes(":rule:")) return;
2600
+ const fieldInfo = parseFieldNodeId(nodeId);
2601
+ if (fieldInfo) {
2602
+ const { instanceId, fieldName } = fieldInfo;
2603
+ const instance = regleDevtoolsRegistry.get(instanceId);
2604
+ if (instance && instance.r$.$fields) {
2605
+ const fieldStatus = resolveFieldByPath(instance.r$.$fields, fieldName);
2606
+ if (fieldStatus && typeof fieldStatus.$validate === "function") fieldStatus.$validate();
2711
2607
  }
2712
- });
2713
- const $silentValue = computed({
2714
- get: () => Object.fromEntries(Object.entries(regles).map(([key, r]) => [key, r.$silentValue])),
2715
- set: (value) => {
2716
- if (typeof value === "object") Object.entries(value).forEach(([key, newValue]) => regles[key].$silentValue = newValue);
2608
+ } else {
2609
+ const instance = regleDevtoolsRegistry.get(nodeId);
2610
+ if (instance && typeof instance.r$.$validate === "function") instance.r$.$validate();
2611
+ }
2612
+ emitInspectorState(api);
2613
+ }
2614
+ function handleResetAction(nodeId, api, resetState = false) {
2615
+ const fieldInfo = parseFieldNodeId(nodeId);
2616
+ if (fieldInfo) {
2617
+ const { instanceId, fieldName } = fieldInfo;
2618
+ const instance = regleDevtoolsRegistry.get(instanceId);
2619
+ if (instance && instance.r$.$fields) {
2620
+ const fieldStatus = resolveFieldByPath(instance.r$.$fields, fieldName);
2621
+ if (fieldStatus && typeof fieldStatus.$reset === "function") fieldStatus.$reset({ toInitialState: resetState });
2717
2622
  }
2623
+ } else {
2624
+ const instance = regleDevtoolsRegistry.get(nodeId);
2625
+ if (instance && typeof instance.r$.$reset === "function") instance.r$.$reset({ toInitialState: resetState });
2626
+ }
2627
+ emitInspectorState(api);
2628
+ }
2629
+ function handleEditInspectorState(payload, api) {
2630
+ const { nodeId, path, state } = payload;
2631
+ if (!path.includes("$value")) return;
2632
+ if (!parseFieldNodeId(nodeId)) return;
2633
+ const [instanceId, _, fieldPath] = nodeId.split(":");
2634
+ const instance = regleDevtoolsRegistry.get(instanceId);
2635
+ if (instance && instance.r$.$fields) {
2636
+ const fieldStatus = resolveFieldByPath(instance.r$.$fields, fieldPath);
2637
+ if (fieldStatus && "$value" in fieldStatus) fieldStatus.$value = state.value;
2638
+ }
2639
+ emitInspectorState(api);
2640
+ }
2641
+ function emitInspectorState(api) {
2642
+ setTimeout(() => {
2643
+ api.sendInspectorState(INSPECTOR_IDS.INSPECTOR);
2644
+ api.sendInspectorTree(INSPECTOR_IDS.INSPECTOR);
2645
+ }, 100);
2646
+ }
2647
+
2648
+ function buildNodeTags(fieldOrR$, componentName) {
2649
+ const tags = [];
2650
+ if (fieldOrR$.$error) tags.push({
2651
+ label: "error",
2652
+ textColor: COLORS.ERROR.text,
2653
+ backgroundColor: COLORS.ERROR.bg
2718
2654
  });
2719
- const $dirty = computed(() => {
2720
- const entries = Object.entries(regles);
2721
- return !!entries.length && entries.every(([_, regle]) => {
2722
- return regle?.$dirty;
2723
- });
2655
+ else if (fieldOrR$.$correct) tags.push({
2656
+ label: "correct",
2657
+ textColor: COLORS.VALID.text,
2658
+ backgroundColor: COLORS.VALID.bg
2724
2659
  });
2725
- const $anyDirty = computed(() => {
2726
- return Object.entries(regles).some(([_, regle]) => {
2727
- return regle?.$anyDirty;
2728
- });
2660
+ if (fieldOrR$.$pending) tags.push({
2661
+ label: "pending",
2662
+ textColor: COLORS.PENDING.text,
2663
+ backgroundColor: COLORS.PENDING.bg
2729
2664
  });
2730
- const $invalid = computed(() => {
2731
- return Object.entries(regles).some(([_, regle]) => {
2732
- return regle?.$invalid;
2733
- });
2665
+ if (fieldOrR$.$dirty) tags.push({
2666
+ label: "dirty",
2667
+ textColor: COLORS.DIRTY.text,
2668
+ backgroundColor: COLORS.DIRTY.bg
2734
2669
  });
2735
- const $correct = computed(() => {
2736
- const entries = Object.entries(regles);
2737
- return !!entries.length && entries.every(([_, regle]) => {
2738
- return regle?.$correct || regle.$anyDirty && !regle.$invalid;
2739
- });
2670
+ else if ("$rules" in fieldOrR$) tags.push({
2671
+ label: "pristine",
2672
+ textColor: COLORS.PRISTINE.text,
2673
+ backgroundColor: COLORS.PRISTINE.bg
2740
2674
  });
2741
- const $error = computed(() => {
2742
- return Object.entries(regles).some(([_, regle]) => {
2743
- return regle?.$error;
2744
- });
2675
+ if (componentName) tags.push({
2676
+ label: componentName,
2677
+ textColor: COLORS.COMPONENT.text,
2678
+ backgroundColor: COLORS.COMPONENT.bg
2745
2679
  });
2746
- const $ready = computed(() => {
2747
- const entries = Object.entries(regles);
2748
- return !!entries.length && entries.every(([_, regle]) => {
2749
- return regle?.$ready;
2750
- });
2680
+ return tags;
2681
+ }
2682
+ function buildRuleTags(rule) {
2683
+ const tags = [];
2684
+ if (!rule.$active) tags.push({
2685
+ label: "inactive",
2686
+ textColor: COLORS.INACTIVE.text,
2687
+ backgroundColor: COLORS.INACTIVE.bg
2751
2688
  });
2752
- const $pending = computed(() => {
2753
- return Object.entries(regles).some(([_, regle]) => {
2754
- return regle?.$pending;
2755
- });
2689
+ else if (!rule.$valid) tags.push({
2690
+ label: "invalid",
2691
+ textColor: COLORS.INVALID.text,
2692
+ backgroundColor: COLORS.INVALID.bg
2756
2693
  });
2757
- const $issues = computed(() => {
2758
- if (scoped) return Object.entries(regles).map(([_, regle]) => {
2759
- return regle.$issues;
2760
- });
2761
- else return Object.fromEntries(Object.entries(regles).map(([key, regle]) => {
2762
- return [key, regle.$issues];
2763
- }));
2764
- });
2765
- const $silentIssues = computed(() => {
2766
- if (scoped) return Object.entries(regles).map(([_, regle]) => {
2767
- return regle.$silentIssues;
2768
- });
2769
- else return Object.fromEntries(Object.entries(regles).map(([key, regle]) => {
2770
- return [key, regle.$silentIssues];
2771
- }));
2772
- });
2773
- const $errors = computed(() => {
2774
- if (scoped) return Object.entries(regles).map(([_, regle]) => {
2775
- return regle.$errors;
2776
- });
2777
- else return Object.fromEntries(Object.entries(regles).map(([key, regle]) => {
2778
- return [key, regle.$errors];
2779
- }));
2780
- });
2781
- const $silentErrors = computed(() => {
2782
- if (scoped) return Object.entries(regles).map(([_, regle]) => {
2783
- return regle.$silentErrors;
2784
- });
2785
- else return Object.fromEntries(Object.entries(regles).map(([key, regle]) => {
2786
- return [key, regle.$silentErrors];
2787
- }));
2788
- });
2789
- const $edited = computed(() => {
2790
- const entries = Object.entries(regles);
2791
- return !!entries.length && entries.every(([_, regle]) => {
2792
- return regle?.$edited;
2793
- });
2794
- });
2795
- const $anyEdited = computed(() => {
2796
- return Object.entries(regles).some(([_, regle]) => {
2797
- return regle?.$anyEdited;
2798
- });
2694
+ else if (rule.$valid) tags.push({
2695
+ label: "valid",
2696
+ textColor: COLORS.VALID.text,
2697
+ backgroundColor: COLORS.VALID.bg
2799
2698
  });
2800
- const $instances = computed(() => {
2801
- if (scoped) return Object.values(regles);
2802
- else return regles;
2699
+ if (rule.$pending) tags.push({
2700
+ label: "pending",
2701
+ textColor: COLORS.PENDING.text,
2702
+ backgroundColor: COLORS.PENDING.bg
2803
2703
  });
2804
- function $reset(options) {
2805
- Object.values(regles).forEach((regle) => {
2806
- regle.$reset(options);
2807
- });
2808
- }
2809
- function $touch() {
2810
- Object.values(regles).forEach((regle) => {
2811
- regle.$touch();
2812
- });
2813
- }
2814
- function $extractDirtyFields(filterNullishValues = true) {
2815
- return Object.values(regles).map((regle) => regle.$extractDirtyFields(filterNullishValues));
2816
- }
2817
- function $clearExternalErrors() {
2818
- Object.values(regles).forEach((regle) => {
2819
- regle.$clearExternalErrors();
2820
- });
2821
- }
2822
- async function $validate(forceValues) {
2823
- try {
2824
- if (forceValues) $value.value = forceValues;
2825
- const data = $value.value;
2826
- return {
2827
- valid: (await Promise.allSettled(Object.values(regles).map((regle) => {
2828
- return regle.$validate();
2829
- }))).every((value) => {
2830
- if (value.status === "fulfilled") return value.value.valid === true;
2831
- else return false;
2832
- }),
2833
- data,
2834
- errors: $errors.value,
2835
- issues: $issues.value
2836
- };
2837
- } catch {
2838
- return {
2839
- valid: false,
2840
- data: $value.value,
2841
- errors: $errors.value,
2842
- issues: $issues.value
2843
- };
2704
+ return tags;
2705
+ }
2706
+ function buildRuleNodes(fieldStatus, instanceId, fieldPath) {
2707
+ const children = [];
2708
+ if (!fieldStatus.$rules || typeof fieldStatus.$rules !== "object") return children;
2709
+ Object.entries(fieldStatus.$rules).forEach(([ruleName, ruleStatus]) => {
2710
+ if (ruleStatus && typeof ruleStatus === "object") {
2711
+ const ruleTags = buildRuleTags(ruleStatus);
2712
+ children.push({
2713
+ id: createRuleNodeId(instanceId, fieldPath, ruleName),
2714
+ label: `⚙️ ${ruleName}`,
2715
+ tags: ruleTags,
2716
+ children: []
2717
+ });
2844
2718
  }
2845
- }
2846
- return reactive({
2847
- ...!scoped && { $silentValue },
2848
- $errors,
2849
- $issues,
2850
- $silentIssues,
2851
- $silentErrors,
2852
- $instances,
2853
- $value,
2854
- $dirty,
2855
- $anyDirty,
2856
- $invalid,
2857
- $correct,
2858
- $error,
2859
- $pending,
2860
- $ready,
2861
- $edited,
2862
- $anyEdited,
2863
- $reset,
2864
- $touch,
2865
- $validate,
2866
- $extractDirtyFields,
2867
- $clearExternalErrors
2868
2719
  });
2720
+ return children;
2869
2721
  }
2870
-
2871
- //#endregion
2872
- //#region src/core/createScopedUseRegle/useCollectScope.ts
2873
- function createUseCollectScope(instances, options) {
2874
- function useCollectScope$1(namespace) {
2875
- const computedNamespace = computed(() => toValue(namespace));
2876
- setEmptyNamespace();
2877
- const r$ = ref(collectRegles(instances.value));
2878
- const regle = reactive({ r$ });
2879
- function setEmptyNamespace() {
2880
- if (computedNamespace.value && !instances.value[computedNamespace.value]) instances.value[computedNamespace.value] = {};
2881
- }
2882
- watch(computedNamespace, setEmptyNamespace);
2883
- watch(instances, (newInstances) => {
2884
- r$.value = collectRegles(newInstances);
2885
- }, { deep: true });
2886
- function collectRegles(r$Instances) {
2887
- if (computedNamespace.value) return mergeRegles(r$Instances[computedNamespace.value] ?? {}, !options.asRecord);
2888
- else return mergeRegles(r$Instances["~~global"] ?? {}, !options.asRecord);
2722
+ function buildCollectionItemNodes(fieldStatus, instanceId, fieldPath) {
2723
+ const children = [];
2724
+ if (!fieldStatus.$each || !Array.isArray(fieldStatus.$each)) return children;
2725
+ fieldStatus.$each.forEach((item, index) => {
2726
+ if (item && typeof item === "object") {
2727
+ const itemTags = buildNodeTags(item);
2728
+ const itemPath = `${fieldPath}[${index}]`;
2729
+ let itemChildren = [];
2730
+ if (isNestedRulesStatus(item)) itemChildren = buildNestedFieldNodes(item.$fields, instanceId, itemPath);
2731
+ else if (isFieldStatus(item)) itemChildren = buildRuleNodes(item, instanceId, itemPath);
2732
+ children.push({
2733
+ id: createFieldNodeId(instanceId, itemPath),
2734
+ label: `[${index}]`,
2735
+ tags: itemTags,
2736
+ children: itemChildren
2737
+ });
2889
2738
  }
2890
- return { r$: regle.r$ };
2891
- }
2892
- return { useCollectScope: useCollectScope$1 };
2739
+ });
2740
+ return children;
2893
2741
  }
2894
-
2895
- //#endregion
2896
- //#region src/core/createScopedUseRegle/useScopedRegle.ts
2897
- function createUseScopedRegleComposable(instances, customUseRegle) {
2898
- const scopedUseRegle = customUseRegle ?? useRegle;
2899
- const useScopedRegle$1 = ((state, rulesFactory, options) => {
2900
- const { namespace, scopeKey: _scopeKey,...restOptions } = options ?? {};
2901
- scopedUseRegle.__config ??= {};
2902
- const computedNamespace = computed(() => toValue(namespace));
2903
- const $id = ref(`${Object.keys(instances.value).length + 1}-${randomId()}`);
2904
- const instanceName = computed(() => {
2905
- return options?.scopeKey ?? `instance-${$id.value}`;
2906
- });
2907
- const { r$ } = scopedUseRegle(state, rulesFactory, restOptions);
2908
- register();
2909
- tryOnScopeDispose(dispose);
2910
- watch(computedNamespace, (newName, oldName) => {
2911
- dispose(oldName);
2912
- register();
2913
- });
2914
- if (getCurrentInstance()) onMounted(() => {
2915
- const currentInstance = getCurrentInstance();
2916
- if (typeof window !== "undefined" && currentInstance?.proxy?.$el?.parentElement) {
2917
- if (document.documentElement && !document.documentElement.contains(currentInstance?.proxy?.$el?.parentElement)) dispose();
2918
- }
2919
- });
2920
- function dispose(oldName) {
2921
- const nameToClean = oldName ?? computedNamespace.value;
2922
- if (nameToClean) {
2923
- if (instances.value[nameToClean]) delete instances.value[nameToClean][instanceName.value];
2924
- } else if (instances.value["~~global"][instanceName.value]) delete instances.value["~~global"][instanceName.value];
2742
+ function buildNestedFieldNodes(fields, instanceId, parentPath) {
2743
+ const children = [];
2744
+ Object.entries(fields).forEach(([fieldName, fieldStatus]) => {
2745
+ if (fieldStatus && typeof fieldStatus === "object") {
2746
+ const fieldPath = parentPath ? `${parentPath}.${fieldName}` : fieldName;
2747
+ const fieldTags = buildNodeTags(fieldStatus);
2748
+ let fieldChildren = [];
2749
+ if (isCollectionRulesStatus(fieldStatus)) fieldChildren = buildCollectionItemNodes(fieldStatus, instanceId, fieldPath);
2750
+ else if (isNestedRulesStatus(fieldStatus)) fieldChildren = buildNestedFieldNodes(fieldStatus.$fields, instanceId, fieldPath);
2751
+ else if (isFieldStatus(fieldStatus)) fieldChildren = buildRuleNodes(fieldStatus, instanceId, fieldPath);
2752
+ children.push({
2753
+ id: createFieldNodeId(instanceId, fieldPath),
2754
+ label: `${fieldName}`,
2755
+ tags: fieldTags,
2756
+ children: fieldChildren
2757
+ });
2925
2758
  }
2926
- function register() {
2927
- if (computedNamespace.value) {
2928
- if (!instances.value[computedNamespace.value]) instances.value[computedNamespace.value] = {};
2929
- instances.value[computedNamespace.value][instanceName.value] = r$;
2930
- } else {
2931
- if (!instances.value["~~global"]) instances.value["~~global"] = {};
2932
- instances.value["~~global"][instanceName.value] = r$;
2933
- }
2759
+ });
2760
+ return children;
2761
+ }
2762
+ function buildRootChildrenNodes(r$, instanceId) {
2763
+ const children = [];
2764
+ if (isFieldStatus(r$)) return buildRuleNodes(r$, instanceId, "root");
2765
+ if (!r$.$fields || typeof r$.$fields !== "object") return children;
2766
+ Object.entries(r$.$fields).forEach(([fieldName, fieldStatus]) => {
2767
+ if (fieldStatus && typeof fieldStatus === "object") {
2768
+ const fieldTags = buildNodeTags(fieldStatus);
2769
+ let fieldChildren = [];
2770
+ if (isCollectionRulesStatus(fieldStatus)) fieldChildren = buildCollectionItemNodes(fieldStatus, instanceId, fieldName);
2771
+ else if (isNestedRulesStatus(fieldStatus)) fieldChildren = buildNestedFieldNodes(fieldStatus.$fields, instanceId, fieldName);
2772
+ else if (isFieldStatus(fieldStatus)) fieldChildren = buildRuleNodes(fieldStatus, instanceId, fieldName);
2773
+ children.push({
2774
+ id: createFieldNodeId(instanceId, fieldName),
2775
+ label: fieldName,
2776
+ tags: fieldTags,
2777
+ children: fieldChildren
2778
+ });
2934
2779
  }
2780
+ });
2781
+ return children;
2782
+ }
2783
+ function buildInspectorTree(instances, filter) {
2784
+ const nodes = instances.map((instance) => {
2785
+ const { r$, id, name, componentName } = instance;
2935
2786
  return {
2936
- r$,
2937
- dispose,
2938
- register
2787
+ id,
2788
+ label: name,
2789
+ tags: buildNodeTags(r$, componentName),
2790
+ children: buildRootChildrenNodes(r$, id)
2939
2791
  };
2940
2792
  });
2941
- return { useScopedRegle: useScopedRegle$1 };
2793
+ if (!filter || filter.trim() === "") return nodes;
2794
+ return filterInspectorTree(nodes, filter);
2942
2795
  }
2943
-
2944
- //#endregion
2945
- //#region src/core/createScopedUseRegle/createScopedUseRegle.ts
2946
- function createScopedUseRegle(options) {
2947
- const instances = (options?.customStore ? () => {
2948
- if (options.customStore) {
2949
- if (!options.customStore?.value["~~global"]) options.customStore.value["~~global"] = {};
2950
- else if (options.customStore?.value) options.customStore.value = { "~~global": {} };
2951
- }
2952
- return options.customStore;
2953
- } : createGlobalState(() => {
2954
- return ref({ "~~global": {} });
2955
- }))();
2956
- const { useScopedRegle: useScopedRegle$1 } = createUseScopedRegleComposable(instances, options?.customUseRegle);
2957
- const { useCollectScope: useCollectScope$1 } = createUseCollectScope(instances, { asRecord: options?.asRecord });
2958
- return {
2959
- useScopedRegle: useScopedRegle$1,
2960
- useCollectScope: useCollectScope$1
2961
- };
2796
+ function filterInspectorTree(nodes, filter) {
2797
+ const lowerFilter = filter.toLowerCase();
2798
+ const filtered = [];
2799
+ for (const node of nodes) {
2800
+ const labelMatches = node.label.toLowerCase().includes(lowerFilter);
2801
+ const tagMatches = node.tags?.some((tag) => tag.label.toLowerCase().includes(lowerFilter)) ?? false;
2802
+ const filteredChildren = node.children ? filterInspectorTree(node.children, filter) : [];
2803
+ if (labelMatches || tagMatches || filteredChildren.length > 0) filtered.push({
2804
+ ...node,
2805
+ children: filteredChildren.length > 0 ? filteredChildren : node.children
2806
+ });
2807
+ }
2808
+ return filtered;
2962
2809
  }
2963
- const { useCollectScope, useScopedRegle } = createScopedUseRegle();
2964
2810
 
2965
- //#endregion
2966
- //#region src/core/variants/createVariant.ts
2967
- /**
2968
- * Declare variations of state that depends on one value
2969
- *
2970
- * Autocomplete may not work here because of https://github.com/microsoft/TypeScript/issues/49547
2971
- *
2972
- * ```ts
2973
- * // ⚠️ Use getter syntax for your rules () => {} or a computed one
2974
- * const {r$} = useRegle(state, () => {
2975
- * const variant = createVariant(state, 'type', [
2976
- * {type: { literal: literal('EMAIL')}, email: { required, email }},
2977
- * {type: { literal: literal('GITHUB')}, username: { required }},
2978
- * {type: { required }},
2979
- * ]);
2980
- *
2981
- * return {
2982
- * ...variant.value,
2983
- * };
2984
- * })
2985
- * ```
2986
- */
2987
- function createVariant(root, discriminantKey, variants) {
2988
- const watchableRoot = computed(() => toValue(root)[discriminantKey]);
2989
- return computed(() => {
2990
- const selectedVariant = variants.find((variant) => {
2991
- if (variant[discriminantKey] && "literal" in variant[discriminantKey]) {
2992
- const literalRule = variant[discriminantKey]["literal"];
2993
- if (isRuleDef(literalRule)) return unref(literalRule._params?.[0]) === watchableRoot.value;
2811
+ function createDevtools(app) {
2812
+ setupDevtoolsPlugin({
2813
+ id: "regle-devtools",
2814
+ label: "Regle",
2815
+ logo: "https://reglejs.dev/logo_main.png",
2816
+ packageName: "@regle/core",
2817
+ homepage: "https://reglejs.dev",
2818
+ componentStateTypes: [],
2819
+ app
2820
+ }, (api) => {
2821
+ api.addInspector({
2822
+ id: INSPECTOR_IDS.INSPECTOR,
2823
+ label: "Regle",
2824
+ noSelectionText: "No instance selected",
2825
+ icon: "rule",
2826
+ treeFilterPlaceholder: "Filter",
2827
+ stateFilterPlaceholder: "Filter validation status",
2828
+ nodeActions: [
2829
+ {
2830
+ icon: "check_circle",
2831
+ tooltip: "Validate (with `$validate`)",
2832
+ action: (nodeId) => {
2833
+ handleValidateAction(nodeId, api);
2834
+ }
2835
+ },
2836
+ {
2837
+ icon: "refresh",
2838
+ tooltip: "Reset validation state (with `$reset`)",
2839
+ action: (nodeId) => {
2840
+ handleResetAction(nodeId, api);
2841
+ }
2842
+ },
2843
+ {
2844
+ icon: "restore",
2845
+ tooltip: "Restore to initial state (with `$reset`)",
2846
+ action: (nodeId) => {
2847
+ handleResetAction(nodeId, api, true);
2848
+ }
2849
+ }
2850
+ ]
2851
+ });
2852
+ setupInstanceWatchers(api);
2853
+ let componentInstances = [];
2854
+ let selectedNodeId = null;
2855
+ api.on.getInspectorTree(async (payload) => {
2856
+ api.unhighlightElement();
2857
+ if (payload.inspectorId === INSPECTOR_IDS.INSPECTOR) {
2858
+ const nodes = buildInspectorTree(regleDevtoolsRegistry.getAll(), payload.filter);
2859
+ if (nodes.length > 0) payload.rootNodes = nodes;
2860
+ else payload.rootNodes = [{
2861
+ id: "empty-regles",
2862
+ label: "No Regles instances found",
2863
+ children: []
2864
+ }];
2865
+ componentInstances = await api.getComponentInstances(app);
2994
2866
  }
2995
2867
  });
2996
- if (selectedVariant) return selectedVariant;
2997
- else {
2998
- const anyDiscriminantRules = variants.find((variant) => isObject(variant[discriminantKey]) && !Object.keys(variant[discriminantKey]).some((key) => key === "literal"));
2999
- if (anyDiscriminantRules) return anyDiscriminantRules;
3000
- else return {};
3001
- }
2868
+ api.on.getInspectorState((payload) => {
2869
+ api.unhighlightElement();
2870
+ if (payload.inspectorId === INSPECTOR_IDS.INSPECTOR) {
2871
+ const state = buildInspectorState(payload.nodeId, (id) => regleDevtoolsRegistry.get(id));
2872
+ const instance = componentInstances.find((instance$1) => {
2873
+ const [componentName] = payload.nodeId.split("#");
2874
+ return instance$1.uid.toString() === componentName;
2875
+ });
2876
+ if (instance?.uid && selectedNodeId !== payload.nodeId) {
2877
+ selectedNodeId = payload.nodeId;
2878
+ if (!parseFieldNodeId(payload.nodeId)) api.highlightElement(instance);
2879
+ }
2880
+ if (state) payload.state = state;
2881
+ else payload.state = {};
2882
+ }
2883
+ });
2884
+ api.on.editInspectorState((payload) => {
2885
+ if (payload.inspectorId === INSPECTOR_IDS.INSPECTOR) handleEditInspectorState(payload, api);
2886
+ });
3002
2887
  });
3003
2888
  }
3004
- /**
3005
- * Narrow a nested variant field to a discriminated value
3006
- *
3007
- * ```ts
3008
- * if (narrowVariant(r$, 'type', 'EMAIL')) {
3009
- * r$.email.$value = 'foo';
3010
- * }
3011
- * ```
3012
- */
3013
- function narrowVariant(root, discriminantKey, discriminantValue) {
3014
- return isObject(root[discriminantKey]) && "$value" in root[discriminantKey] && root[discriminantKey]?.$value === discriminantValue;
3015
- }
3016
- function variantToRef(root, discriminantKey, discriminantValue, _options) {
3017
- const fromRoot = isRef(root) ? toRef(root.value, "$fields") : toRef(root, "$fields");
3018
- const returnedRef = ref();
3019
- watch(fromRoot, async () => {
3020
- await nextTick();
3021
- if (narrowVariant(fromRoot.value, discriminantKey, discriminantValue)) returnedRef.value = toRef(root).value;
3022
- else returnedRef.value = void 0;
3023
- }, {
3024
- immediate: true,
3025
- flush: "pre"
2889
+ function setupInstanceWatchers(api) {
2890
+ const watchedInstances = /* @__PURE__ */ new Set();
2891
+ const setupWatchers = () => {
2892
+ regleDevtoolsRegistry.getAll().forEach((instance) => {
2893
+ const { r$, id } = instance;
2894
+ if (watchedInstances.has(id)) return;
2895
+ const stopHandle = watchRegleInstance(id, r$, () => {
2896
+ api.sendInspectorState(INSPECTOR_IDS.INSPECTOR);
2897
+ api.sendInspectorTree(INSPECTOR_IDS.INSPECTOR);
2898
+ });
2899
+ regleDevtoolsRegistry.addWatcher(id, stopHandle);
2900
+ watchedInstances.add(id);
2901
+ });
2902
+ };
2903
+ setupWatchers();
2904
+ regleDevtoolsRegistry.onInstancesChange(() => {
2905
+ const currentIds = new Set(regleDevtoolsRegistry.getAll().map((i) => i.id));
2906
+ for (const id of watchedInstances) if (!currentIds.has(id)) watchedInstances.delete(id);
2907
+ api.sendInspectorTree(INSPECTOR_IDS.INSPECTOR);
2908
+ api.sendInspectorState(INSPECTOR_IDS.INSPECTOR);
2909
+ setupWatchers();
3026
2910
  });
3027
- return returnedRef;
3028
- }
3029
-
3030
- //#endregion
3031
- //#region src/core/refineRules.ts
3032
- /**
3033
- * Helper method to wrap an raw rules object
3034
- *
3035
- * Similar to:
3036
- *
3037
- * ```ts
3038
- * const rules = {...} satisfies RegleUnknownRulesTree
3039
- * ```
3040
- */
3041
- function defineRules(rules) {
3042
- return rules;
3043
- }
3044
- /**
3045
- * Refine a raw rules object to set rules that depends on the state values.
3046
- *
3047
- * @example
3048
- *
3049
- * ```ts
3050
- * const rules = refineRules({
3051
- * password: { required, type: type<string>() },
3052
- * }, (state) => {
3053
- * return {
3054
- * confirmPassword: { required, sameAs: sameAs(() => state.value.password)}
3055
- * }
3056
- * })
3057
- * ```
3058
- */
3059
- function refineRules(rules, refinement) {
3060
- return (state) => merge({ ...rules }, refinement(state));
3061
2911
  }
3062
2912
 
3063
- //#endregion
3064
- //#region src/devtools/constants.ts
3065
- const COLORS = {
3066
- ERROR: {
3067
- text: 16777215,
3068
- bg: 15680580
3069
- },
3070
- INVALID: {
3071
- text: 16777215,
3072
- bg: 16281969
3073
- },
3074
- VALID: {
3075
- text: 16777215,
3076
- bg: 1096065
3077
- },
3078
- PENDING: {
3079
- text: 16777215,
3080
- bg: 16096779
3081
- },
3082
- DIRTY: {
3083
- text: 2042167,
3084
- bg: 16708551
3085
- },
3086
- COMPONENT: {
3087
- text: 16777215,
3088
- bg: 6514417
2913
+ function useRootStorage({ initialState, originalState, options, scopeRules, state, customRules, shortcuts, schemaErrors, schemaMode = false, onValidate }) {
2914
+ const storage = useStorage();
2915
+ const regle = ref();
2916
+ const computedExternalErrors = ref();
2917
+ let $unwatchExternalErrors;
2918
+ let $unwatchComputedExternalErrors;
2919
+ function defineExternalErrorsWatchSource() {
2920
+ $unwatchExternalErrors = watch(() => options.externalErrors?.value, () => {
2921
+ $unwatchComputedExternalErrors?.();
2922
+ if (options.externalErrors?.value && Object.keys(options.externalErrors.value).some((key) => key.includes("."))) computedExternalErrors.value = dotPathObjectToNested(options.externalErrors.value);
2923
+ else computedExternalErrors.value = options.externalErrors?.value;
2924
+ defineComputedExternalErrorsWatchSource();
2925
+ }, {
2926
+ immediate: true,
2927
+ deep: true
2928
+ });
3089
2929
  }
3090
- };
3091
- const PRIORITY_KEYS = {
3092
- ROOT: [
3093
- "$invalid",
3094
- "$dirty",
3095
- "$error",
3096
- "$pending",
3097
- "$valid",
3098
- "$ready"
3099
- ],
3100
- FIELD: [
3101
- "$value",
3102
- "$invalid",
3103
- "$dirty",
3104
- "$error",
3105
- "$pending",
3106
- "$errors"
3107
- ]
3108
- };
3109
- const INSPECTOR_IDS = {
3110
- INSPECTOR: "regle-inspector",
3111
- TIMELINE: "regle-timeline"
3112
- };
3113
-
3114
- //#endregion
3115
- //#region src/devtools/utils.ts
3116
- function isMethod(value) {
3117
- return typeof value === "function";
3118
- }
3119
- function getPriorityProperties(obj, keys) {
3120
- return keys.filter((key) => key in obj && !isMethod(obj[key])).map((key) => {
3121
- let editable = false;
3122
- if (key === "$value") editable = true;
3123
- return {
3124
- key,
3125
- value: obj[key],
3126
- editable
3127
- };
2930
+ function defineComputedExternalErrorsWatchSource() {
2931
+ $unwatchComputedExternalErrors = watch(() => computedExternalErrors.value, () => {
2932
+ $unwatchExternalErrors?.();
2933
+ if (options.externalErrors?.value) options.externalErrors.value = computedExternalErrors.value;
2934
+ defineExternalErrorsWatchSource();
2935
+ }, { deep: true });
2936
+ }
2937
+ defineExternalErrorsWatchSource();
2938
+ if (isNestedRulesDef(state, scopeRules)) regle.value = createReactiveNestedStatus({
2939
+ rootRules: scopeRules,
2940
+ rulesDef: scopeRules,
2941
+ state,
2942
+ customMessages: customRules?.(),
2943
+ storage,
2944
+ options,
2945
+ externalErrors: computedExternalErrors,
2946
+ validationGroups: options.validationGroups,
2947
+ initialState,
2948
+ originalState,
2949
+ shortcuts,
2950
+ fieldName: "root",
2951
+ path: "",
2952
+ cachePath: "",
2953
+ schemaErrors,
2954
+ rootSchemaErrors: schemaErrors,
2955
+ schemaMode,
2956
+ onValidate
3128
2957
  });
3129
- }
3130
- function getRemainingProperties(obj, excludeKeys) {
3131
- return Object.entries(obj).filter(([key]) => !excludeKeys.includes(key) && key.startsWith("$") && !isMethod(obj[key])).map(([key, value]) => ({
3132
- key,
3133
- value,
3134
- editable: false
3135
- }));
3136
- }
3137
- function parseFieldNodeId(nodeId) {
3138
- if (!nodeId.includes(":field:")) return null;
3139
- const [instanceId, , fieldName] = nodeId.split(":");
3140
- return {
3141
- instanceId,
3142
- fieldName
3143
- };
3144
- }
3145
- function createFieldNodeId(instanceId, fieldName) {
3146
- return `${instanceId}:field:${fieldName}`;
3147
- }
3148
- function parseRuleNodeId(nodeId) {
3149
- if (!nodeId.includes(":rule:")) return null;
3150
- const parts = nodeId.split(":rule:");
3151
- if (parts.length !== 2) return null;
3152
- const fieldPart = parts[0];
3153
- const ruleName = parts[1];
3154
- const fieldInfo = parseFieldNodeId(fieldPart);
3155
- if (!fieldInfo) return null;
3156
- return {
3157
- ...fieldInfo,
3158
- ruleName
3159
- };
3160
- }
3161
- function createRuleNodeId(instanceId, fieldName, ruleName) {
3162
- return `${createFieldNodeId(instanceId, fieldName)}:rule:${ruleName}`;
2958
+ else if (isValidatorRulesDef(scopeRules)) regle.value = createReactiveFieldStatus({
2959
+ rulesDef: scopeRules,
2960
+ state,
2961
+ customMessages: customRules?.(),
2962
+ storage,
2963
+ options,
2964
+ externalErrors: computedExternalErrors,
2965
+ initialState,
2966
+ originalState,
2967
+ shortcuts,
2968
+ fieldName: "root",
2969
+ path: "",
2970
+ cachePath: "",
2971
+ schemaMode,
2972
+ schemaErrors,
2973
+ onValidate
2974
+ });
2975
+ if (getCurrentScope()) onScopeDispose(() => {
2976
+ regle.value?.$unwatch();
2977
+ $unwatchComputedExternalErrors?.();
2978
+ $unwatchExternalErrors?.();
2979
+ });
2980
+ if (typeof window !== "undefined" && regle.value) registerRegleInstance(regle.value, { name: toValue(options.id) });
2981
+ return reactive({ regle });
3163
2982
  }
3164
2983
 
3165
- //#endregion
3166
- //#region src/devtools/state-builder.ts
3167
- /**
3168
- * Build state for a field node
3169
- */
3170
- function buildFieldState(fieldStatus) {
3171
- const state = {};
3172
- const priorityProperties = getPriorityProperties(fieldStatus, PRIORITY_KEYS.FIELD);
3173
- if (priorityProperties.length > 0) state["State"] = priorityProperties;
3174
- const remainingProperties = getRemainingProperties(fieldStatus, [
3175
- ...PRIORITY_KEYS.FIELD,
3176
- "$rules",
3177
- "$fields"
3178
- ]);
3179
- if (remainingProperties.length > 0) state["Other Properties"] = remainingProperties;
3180
- return state;
3181
- }
3182
- /**
3183
- * Build state for a rule node
3184
- */
3185
- function buildRuleState(ruleStatus) {
3186
- const state = {};
3187
- const ruleKeys = [
3188
- "$type",
3189
- "$valid",
3190
- "$active",
3191
- "$pending",
3192
- "$message",
3193
- "$tooltip"
3194
- ];
3195
- const priorityProperties = getPriorityProperties(ruleStatus, ruleKeys);
3196
- if (priorityProperties.length > 0) state["Rule State"] = priorityProperties;
3197
- if (ruleStatus.$params && Array.isArray(ruleStatus.$params) && ruleStatus.$params.length > 0) state["Parameters"] = [{
3198
- key: "$params",
3199
- value: ruleStatus.$params,
3200
- editable: false
3201
- }];
3202
- if (ruleStatus.$metadata !== void 0 && ruleStatus.$metadata !== true && ruleStatus.$metadata !== false) state["Metadata"] = [{
3203
- key: "$metadata",
3204
- value: ruleStatus.$metadata,
3205
- editable: false
3206
- }];
3207
- const remainingProperties = getRemainingProperties(ruleStatus, [
3208
- ...ruleKeys,
3209
- "$params",
3210
- "$metadata",
3211
- "$validator",
3212
- "$parse",
3213
- "$reset",
3214
- "$unwatch",
3215
- "$watch",
3216
- "$haveAsync",
3217
- "$validating",
3218
- "$fieldDirty",
3219
- "$fieldInvalid",
3220
- "$fieldPending",
3221
- "$fieldCorrect",
3222
- "$fieldError",
3223
- "$maybePending",
3224
- "$externalErrors"
3225
- ]);
3226
- if (remainingProperties.length > 0) state["Other Properties"] = remainingProperties;
3227
- return state;
3228
- }
3229
- function buildRootState(r$) {
3230
- const state = {};
3231
- const priorityProperties = getPriorityProperties(r$, PRIORITY_KEYS.ROOT);
3232
- if (priorityProperties.length > 0) state["State"] = priorityProperties;
3233
- const remainingProperties = getRemainingProperties(r$, [...PRIORITY_KEYS.ROOT, "$fields"]);
3234
- if (remainingProperties.length > 0) state["Other Properties"] = remainingProperties;
3235
- return state;
2984
+ function createRootRegleLogic({ state, rulesFactory, options, globalOptions, customRules, shortcuts }) {
2985
+ const definedRules = isRef(rulesFactory) ? rulesFactory : typeof rulesFactory === "function" ? void 0 : computed(() => rulesFactory);
2986
+ const resolvedOptions = {
2987
+ ...globalOptions,
2988
+ ...options
2989
+ };
2990
+ let unwatchRules;
2991
+ const watchableRulesGetters = shallowRef(definedRules ?? {});
2992
+ if (typeof rulesFactory === "function") unwatchRules = watchEffect(() => {
2993
+ watchableRulesGetters.value = rulesFactory(state);
2994
+ triggerRef(watchableRulesGetters);
2995
+ });
2996
+ const initialState = ref(isObject(state.value) ? { ...cloneDeep(state.value) } : cloneDeep(state.value));
2997
+ const originalState = isObject(state.value) ? { ...cloneDeep(state.value) } : cloneDeep(state.value);
2998
+ tryOnScopeDispose(() => {
2999
+ unwatchRules?.();
3000
+ });
3001
+ return useRootStorage({
3002
+ scopeRules: watchableRulesGetters,
3003
+ state,
3004
+ options: resolvedOptions,
3005
+ initialState,
3006
+ originalState,
3007
+ customRules,
3008
+ shortcuts
3009
+ });
3236
3010
  }
3237
- function resolveFieldByPath(fields, path) {
3238
- if (!fields || !path) return null;
3239
- const segments = path.match(/[^.\[\]]+/g);
3240
- if (!segments) return null;
3241
- let current = fields;
3242
- for (const segment of segments) {
3243
- if (!current) return null;
3244
- const index = parseInt(segment);
3245
- if (!isNaN(index)) if (isCollectionRulesStatus(current) && Array.isArray(current.$each)) current = current.$each[index];
3246
- else return null;
3247
- else if (isNestedRulesStatus(current) && current.$fields && current.$fields[segment]) current = current.$fields[segment];
3248
- else if (current[segment]) current = current[segment];
3249
- else return null;
3011
+
3012
+ function createUseRegleComposable(customRules, options, shortcuts) {
3013
+ const globalOptions = {
3014
+ autoDirty: options?.autoDirty,
3015
+ lazy: options?.lazy,
3016
+ rewardEarly: options?.rewardEarly,
3017
+ silent: options?.silent,
3018
+ clearExternalErrorsOnChange: options?.clearExternalErrorsOnChange
3019
+ };
3020
+ function useRegle$1(state, rulesFactory, options$1) {
3021
+ return { r$: createRootRegleLogic({
3022
+ state: isRef(state) ? state : ref(state),
3023
+ rulesFactory,
3024
+ options: options$1,
3025
+ globalOptions,
3026
+ customRules,
3027
+ shortcuts
3028
+ }).regle };
3250
3029
  }
3251
- return current;
3030
+ return useRegle$1;
3252
3031
  }
3253
- function buildInspectorState(nodeId, getInstance) {
3254
- const ruleInfo = parseRuleNodeId(nodeId);
3255
- if (ruleInfo) {
3256
- const { instanceId, fieldName, ruleName } = ruleInfo;
3257
- const instance$1 = getInstance(instanceId);
3258
- if (!instance$1 || !instance$1.r$.$fields) return null;
3259
- const fieldStatus = resolveFieldByPath(instance$1.r$.$fields, fieldName);
3260
- if (!fieldStatus || !("$rules" in fieldStatus)) return null;
3261
- const ruleStatus = fieldStatus.$rules[ruleName];
3262
- if (!ruleStatus) return null;
3263
- return buildRuleState(ruleStatus);
3264
- }
3265
- const fieldInfo = parseFieldNodeId(nodeId);
3266
- if (fieldInfo) {
3267
- const { instanceId, fieldName } = fieldInfo;
3268
- const instance$1 = getInstance(instanceId);
3269
- if (!instance$1 || !instance$1.r$.$fields) return null;
3270
- const fieldStatus = resolveFieldByPath(instance$1.r$.$fields, fieldName);
3271
- if (!fieldStatus) return null;
3272
- return buildFieldState(fieldStatus);
3032
+ /**
3033
+ * useRegle serves as the foundation for validation logic.
3034
+ *
3035
+ * It accepts the following inputs:
3036
+ *
3037
+ * @param state - This can be a plain object, a ref, a reactive object, or a structure containing nested refs.
3038
+ * @param rules - These should align with the structure of your state.
3039
+ * @param modifiers - Customize regle behaviour
3040
+ *
3041
+ * ```ts
3042
+ * import { useRegle } from '@regle/core';
3043
+ import { required } from '@regle/rules';
3044
+
3045
+ const { r$ } = useRegle({ email: '' }, {
3046
+ email: { required }
3047
+ })
3048
+ * ```
3049
+ * Docs: {@link https://reglejs.dev/core-concepts/}
3050
+ */
3051
+ const useRegle = createUseRegleComposable();
3052
+
3053
+ function createInferRuleHelper() {
3054
+ function inferRules$1(state, rulesFactory) {
3055
+ return rulesFactory;
3273
3056
  }
3274
- const instance = getInstance(nodeId);
3275
- if (!instance) return null;
3276
- return buildRootState(instance.r$);
3057
+ return inferRules$1;
3277
3058
  }
3059
+ /**
3060
+ * Rule type helper to provide autocomplete and typecheck to your form rules or part of your form rules
3061
+ * It will just return the rules without any processing.
3062
+ *
3063
+ * @param state - The state reference
3064
+ * @param rules - Your rule tree
3065
+ */
3066
+ const inferRules = createInferRuleHelper();
3278
3067
 
3279
- //#endregion
3280
- //#region src/devtools/actions.ts
3281
- function handleValidateAction(nodeId, api) {
3282
- if (nodeId.includes(":rule:")) return;
3283
- const fieldInfo = parseFieldNodeId(nodeId);
3284
- if (fieldInfo) {
3285
- const { instanceId, fieldName } = fieldInfo;
3286
- const instance = regleDevtoolsRegistry.get(instanceId);
3287
- if (instance && instance.r$.$fields) {
3288
- const fieldStatus = resolveFieldByPath(instance.r$.$fields, fieldName);
3289
- if (fieldStatus && typeof fieldStatus.$validate === "function") fieldStatus.$validate();
3290
- }
3291
- } else {
3292
- const instance = regleDevtoolsRegistry.get(nodeId);
3293
- if (instance && typeof instance.r$.$validate === "function") instance.r$.$validate();
3068
+ function createEmptyRuleState(rules) {
3069
+ const result = {};
3070
+ if (Object.entries(rules).some(([_, rule]) => isRuleDef(rule) || typeof rule === "function")) return null;
3071
+ for (const key in rules) {
3072
+ const item = rules[key];
3073
+ if (!!item && isObject(item) && "$each" in item && item["$each"] && isObject(item["$each"])) result[key] = [createEmptyRuleState(item["$each"])];
3074
+ else if (isObject(item) && !isEmpty(item) && !Object.entries(item).some(([_, rule]) => isRuleDef(rule) || typeof rule === "function")) result[key] = createEmptyRuleState(item);
3075
+ else result[key] = null;
3294
3076
  }
3295
- emitInspectorState(api);
3077
+ return result;
3296
3078
  }
3297
- function handleResetAction(nodeId, api, resetState = false) {
3298
- const fieldInfo = parseFieldNodeId(nodeId);
3299
- if (fieldInfo) {
3300
- const { instanceId, fieldName } = fieldInfo;
3301
- const instance = regleDevtoolsRegistry.get(instanceId);
3302
- if (instance && instance.r$.$fields) {
3303
- const fieldStatus = resolveFieldByPath(instance.r$.$fields, fieldName);
3304
- if (fieldStatus && typeof fieldStatus.$reset === "function") fieldStatus.$reset({ toInitialState: resetState });
3305
- }
3306
- } else {
3307
- const instance = regleDevtoolsRegistry.get(nodeId);
3308
- if (instance && typeof instance.r$.$reset === "function") instance.r$.$reset({ toInitialState: resetState });
3079
+ function createUseRulesComposable(customRules, options, shortcuts) {
3080
+ const globalOptions = {
3081
+ autoDirty: options?.autoDirty,
3082
+ lazy: options?.lazy,
3083
+ rewardEarly: options?.rewardEarly,
3084
+ silent: options?.silent,
3085
+ clearExternalErrorsOnChange: options?.clearExternalErrorsOnChange
3086
+ };
3087
+ function useRules$1(rulesFactory, options$1) {
3088
+ const definedRules = isRef(rulesFactory) ? rulesFactory : typeof rulesFactory === "function" ? void 0 : computed(() => rulesFactory);
3089
+ return createRootRegleLogic({
3090
+ state: ref(createEmptyRuleState(definedRules?.value)),
3091
+ rulesFactory: definedRules,
3092
+ options: options$1,
3093
+ globalOptions,
3094
+ customRules,
3095
+ shortcuts
3096
+ }).regle;
3309
3097
  }
3310
- emitInspectorState(api);
3098
+ return useRules$1;
3311
3099
  }
3312
- function handleEditInspectorState(payload, api) {
3313
- const { nodeId, path, state } = payload;
3314
- if (!path.includes("$value")) return;
3315
- if (!parseFieldNodeId(nodeId)) return;
3316
- const [instanceId, _, fieldPath] = nodeId.split(":");
3317
- const instance = regleDevtoolsRegistry.get(instanceId);
3318
- if (instance && instance.r$.$fields) {
3319
- const fieldStatus = resolveFieldByPath(instance.r$.$fields, fieldPath);
3320
- if (fieldStatus && "$value" in fieldStatus) fieldStatus.$value = state.value;
3321
- }
3322
- emitInspectorState(api);
3100
+ /**
3101
+ * useRules is a clone of useRegle, without the need to provide a state.
3102
+ *
3103
+ * It accepts the following inputs:
3104
+ *
3105
+ * @param rules - Your rules object
3106
+ * @param modifiers - Customize regle behaviour
3107
+ *
3108
+ * ```ts
3109
+ * import { useRules } from '@regle/core';
3110
+ import { required } from '@regle/rules';
3111
+
3112
+ const { r$ } = useRules({
3113
+ email: { required }
3114
+ })
3115
+ * ```
3116
+ */
3117
+ const useRules = createUseRulesComposable();
3118
+
3119
+ /**
3120
+ * Define a global regle configuration, where you can:
3121
+ * - Customize built-in rules messages
3122
+ * - Add your custom rules
3123
+ * - Define global modifiers
3124
+ * - Define shortcuts
3125
+ *
3126
+ * It will return:
3127
+ *
3128
+ * - a `useRegle` composable that can typecheck your custom rules
3129
+ * - an `inferRules` helper that can typecheck your custom rules
3130
+ */
3131
+ function defineRegleConfig({ rules, modifiers, shortcuts }) {
3132
+ const useRegle$1 = createUseRegleComposable(rules, modifiers, shortcuts);
3133
+ const useRules$1 = createUseRulesComposable(rules, modifiers, shortcuts);
3134
+ useRegle$1.__config = {
3135
+ rules,
3136
+ modifiers,
3137
+ shortcuts
3138
+ };
3139
+ useRules$1.__config = {
3140
+ rules,
3141
+ modifiers,
3142
+ shortcuts
3143
+ };
3144
+ return {
3145
+ useRegle: useRegle$1,
3146
+ inferRules: createInferRuleHelper(),
3147
+ useRules: useRules$1
3148
+ };
3323
3149
  }
3324
- function emitInspectorState(api) {
3325
- setTimeout(() => {
3326
- api.sendInspectorState(INSPECTOR_IDS.INSPECTOR);
3327
- api.sendInspectorTree(INSPECTOR_IDS.INSPECTOR);
3328
- }, 100);
3150
+ /**
3151
+ * Extend an already created custom `useRegle` (as the first parameter)
3152
+ *
3153
+ * It will return:
3154
+ *
3155
+ * - a `useRegle` composable that can typecheck your custom rules
3156
+ * - an `inferRules` helper that can typecheck your custom rules
3157
+ */
3158
+ function extendRegleConfig(regle, { rules, modifiers, shortcuts }) {
3159
+ const rootConfig = regle.__config ?? {};
3160
+ const newRules = () => ({
3161
+ ...rootConfig.rules?.(),
3162
+ ...rules?.()
3163
+ });
3164
+ const newModifiers = rootConfig.modifiers && modifiers ? merge(rootConfig.modifiers, modifiers) : rootConfig.modifiers ?? modifiers;
3165
+ const newShortcuts = rootConfig.shortcuts && shortcuts ? merge(rootConfig.shortcuts, shortcuts) : rootConfig.shortcuts ?? shortcuts;
3166
+ const useRegle$1 = createUseRegleComposable(newRules, newModifiers, newShortcuts);
3167
+ useRegle$1.__config = {
3168
+ rules: newRules,
3169
+ modifiers: newModifiers,
3170
+ shortcuts: newShortcuts
3171
+ };
3172
+ return {
3173
+ useRegle: useRegle$1,
3174
+ inferRules: createInferRuleHelper()
3175
+ };
3329
3176
  }
3330
3177
 
3331
- //#endregion
3332
- //#region src/devtools/tree-builder.ts
3333
- function buildNodeTags(fieldOrR$, componentName) {
3334
- const tags = [];
3335
- if (fieldOrR$.$error) tags.push({
3336
- label: "error",
3337
- textColor: COLORS.ERROR.text,
3338
- backgroundColor: COLORS.ERROR.bg
3178
+ function mergeRegles(regles, _scoped) {
3179
+ const scoped = _scoped == null ? false : _scoped;
3180
+ const $value = computed({
3181
+ get: () => {
3182
+ if (scoped) return Object.values(regles).map((r) => r.$value);
3183
+ else return Object.fromEntries(Object.entries(regles).map(([key, r]) => [key, r.$value]));
3184
+ },
3185
+ set: (value) => {
3186
+ if (!scoped) {
3187
+ if (typeof value === "object") Object.entries(value).forEach(([key, newValue]) => regles[key].$value = newValue);
3188
+ }
3189
+ }
3190
+ });
3191
+ const $silentValue = computed({
3192
+ get: () => Object.fromEntries(Object.entries(regles).map(([key, r]) => [key, r.$silentValue])),
3193
+ set: (value) => {
3194
+ if (typeof value === "object") Object.entries(value).forEach(([key, newValue]) => regles[key].$silentValue = newValue);
3195
+ }
3339
3196
  });
3340
- else if (fieldOrR$.$correct) tags.push({
3341
- label: "correct",
3342
- textColor: COLORS.VALID.text,
3343
- backgroundColor: COLORS.VALID.bg
3197
+ const $dirty = computed(() => {
3198
+ const entries = Object.entries(regles);
3199
+ return !!entries.length && entries.every(([_, regle]) => {
3200
+ return regle?.$dirty;
3201
+ });
3344
3202
  });
3345
- if (fieldOrR$.$pending) tags.push({
3346
- label: "pending",
3347
- textColor: COLORS.PENDING.text,
3348
- backgroundColor: COLORS.PENDING.bg
3203
+ const $anyDirty = computed(() => {
3204
+ return Object.entries(regles).some(([_, regle]) => {
3205
+ return regle?.$anyDirty;
3206
+ });
3349
3207
  });
3350
- if (fieldOrR$.$dirty) tags.push({
3351
- label: "dirty",
3352
- textColor: COLORS.DIRTY.text,
3353
- backgroundColor: COLORS.DIRTY.bg
3208
+ const $invalid = computed(() => {
3209
+ return Object.entries(regles).some(([_, regle]) => {
3210
+ return regle?.$invalid;
3211
+ });
3354
3212
  });
3355
- if (componentName) tags.push({
3356
- label: componentName,
3357
- textColor: COLORS.COMPONENT.text,
3358
- backgroundColor: COLORS.COMPONENT.bg
3213
+ const $correct = computed(() => {
3214
+ const entries = Object.entries(regles);
3215
+ return !!entries.length && entries.every(([_, regle]) => {
3216
+ return regle?.$correct || regle.$anyDirty && !regle.$invalid;
3217
+ });
3359
3218
  });
3360
- return tags;
3361
- }
3362
- function buildRuleTags(rule) {
3363
- const tags = [];
3364
- if (!rule.$active) tags.push({
3365
- label: "inactive",
3366
- textColor: 10265519,
3367
- backgroundColor: 15987958
3219
+ const $error = computed(() => {
3220
+ return Object.entries(regles).some(([_, regle]) => {
3221
+ return regle?.$error;
3222
+ });
3368
3223
  });
3369
- else if (!rule.$valid) tags.push({
3370
- label: "invalid",
3371
- textColor: COLORS.INVALID.text,
3372
- backgroundColor: COLORS.INVALID.bg
3224
+ const $ready = computed(() => {
3225
+ const entries = Object.entries(regles);
3226
+ return !!entries.length && entries.every(([_, regle]) => {
3227
+ return regle?.$ready;
3228
+ });
3373
3229
  });
3374
- else if (rule.$valid) tags.push({
3375
- label: "valid",
3376
- textColor: COLORS.VALID.text,
3377
- backgroundColor: COLORS.VALID.bg
3230
+ const $pending = computed(() => {
3231
+ return Object.entries(regles).some(([_, regle]) => {
3232
+ return regle?.$pending;
3233
+ });
3378
3234
  });
3379
- if (rule.$pending) tags.push({
3380
- label: "pending",
3381
- textColor: COLORS.PENDING.text,
3382
- backgroundColor: COLORS.PENDING.bg
3235
+ const $issues = computed(() => {
3236
+ if (scoped) return Object.entries(regles).map(([_, regle]) => {
3237
+ return regle.$issues;
3238
+ });
3239
+ else return Object.fromEntries(Object.entries(regles).map(([key, regle]) => {
3240
+ return [key, regle.$issues];
3241
+ }));
3383
3242
  });
3384
- return tags;
3385
- }
3386
- function buildRuleNodes(fieldStatus, instanceId, fieldPath) {
3387
- const children = [];
3388
- if (!fieldStatus.$rules || typeof fieldStatus.$rules !== "object") return children;
3389
- Object.entries(fieldStatus.$rules).forEach(([ruleName, ruleStatus]) => {
3390
- if (ruleStatus && typeof ruleStatus === "object") {
3391
- const ruleTags = buildRuleTags(ruleStatus);
3392
- children.push({
3393
- id: createRuleNodeId(instanceId, fieldPath, ruleName),
3394
- label: ruleName,
3395
- tags: ruleTags,
3396
- children: []
3397
- });
3398
- }
3243
+ const $silentIssues = computed(() => {
3244
+ if (scoped) return Object.entries(regles).map(([_, regle]) => {
3245
+ return regle.$silentIssues;
3246
+ });
3247
+ else return Object.fromEntries(Object.entries(regles).map(([key, regle]) => {
3248
+ return [key, regle.$silentIssues];
3249
+ }));
3399
3250
  });
3400
- return children;
3401
- }
3402
- function buildCollectionItemNodes(fieldStatus, instanceId, fieldPath) {
3403
- const children = [];
3404
- if (!fieldStatus.$each || !Array.isArray(fieldStatus.$each)) return children;
3405
- fieldStatus.$each.forEach((item, index) => {
3406
- if (item && typeof item === "object") {
3407
- const itemTags = buildNodeTags(item);
3408
- const itemPath = `${fieldPath}[${index}]`;
3409
- let itemChildren = [];
3410
- if (isNestedRulesStatus(item)) itemChildren = buildNestedFieldNodes(item.$fields, instanceId, itemPath);
3411
- else if (isFieldStatus(item)) itemChildren = buildRuleNodes(item, instanceId, itemPath);
3412
- children.push({
3413
- id: createFieldNodeId(instanceId, itemPath),
3414
- label: `[${index}]`,
3415
- tags: itemTags,
3416
- children: itemChildren
3417
- });
3418
- }
3251
+ const $errors = computed(() => {
3252
+ if (scoped) return Object.entries(regles).map(([_, regle]) => {
3253
+ return regle.$errors;
3254
+ });
3255
+ else return Object.fromEntries(Object.entries(regles).map(([key, regle]) => {
3256
+ return [key, regle.$errors];
3257
+ }));
3419
3258
  });
3420
- return children;
3421
- }
3422
- function buildNestedFieldNodes(fields, instanceId, parentPath) {
3423
- const children = [];
3424
- Object.entries(fields).forEach(([fieldName, fieldStatus]) => {
3425
- if (fieldStatus && typeof fieldStatus === "object") {
3426
- const fieldPath = parentPath ? `${parentPath}.${fieldName}` : fieldName;
3427
- const fieldTags = buildNodeTags(fieldStatus);
3428
- let fieldChildren = [];
3429
- if (isCollectionRulesStatus(fieldStatus)) fieldChildren = buildCollectionItemNodes(fieldStatus, instanceId, fieldPath);
3430
- else if (isNestedRulesStatus(fieldStatus)) fieldChildren = buildNestedFieldNodes(fieldStatus.$fields, instanceId, fieldPath);
3431
- else if (isFieldStatus(fieldStatus)) fieldChildren = buildRuleNodes(fieldStatus, instanceId, fieldPath);
3432
- children.push({
3433
- id: createFieldNodeId(instanceId, fieldPath),
3434
- label: fieldName,
3435
- tags: fieldTags,
3436
- children: fieldChildren
3437
- });
3438
- }
3259
+ const $silentErrors = computed(() => {
3260
+ if (scoped) return Object.entries(regles).map(([_, regle]) => {
3261
+ return regle.$silentErrors;
3262
+ });
3263
+ else return Object.fromEntries(Object.entries(regles).map(([key, regle]) => {
3264
+ return [key, regle.$silentErrors];
3265
+ }));
3439
3266
  });
3440
- return children;
3441
- }
3442
- function buildRootChildrenNodes(r$, instanceId) {
3443
- const children = [];
3444
- if (isFieldStatus(r$)) return buildRuleNodes(r$, instanceId, "root");
3445
- if (!r$.$fields || typeof r$.$fields !== "object") return children;
3446
- Object.entries(r$.$fields).forEach(([fieldName, fieldStatus]) => {
3447
- if (fieldStatus && typeof fieldStatus === "object") {
3448
- const fieldTags = buildNodeTags(fieldStatus);
3449
- let fieldChildren = [];
3450
- if (isCollectionRulesStatus(fieldStatus)) fieldChildren = buildCollectionItemNodes(fieldStatus, instanceId, fieldName);
3451
- else if (isNestedRulesStatus(fieldStatus)) fieldChildren = buildNestedFieldNodes(fieldStatus.$fields, instanceId, fieldName);
3452
- else if (isFieldStatus(fieldStatus)) fieldChildren = buildRuleNodes(fieldStatus, instanceId, fieldName);
3453
- children.push({
3454
- id: createFieldNodeId(instanceId, fieldName),
3455
- label: fieldName,
3456
- tags: fieldTags,
3457
- children: fieldChildren
3458
- });
3459
- }
3267
+ const $edited = computed(() => {
3268
+ const entries = Object.entries(regles);
3269
+ return !!entries.length && entries.every(([_, regle]) => {
3270
+ return regle?.$edited;
3271
+ });
3460
3272
  });
3461
- return children;
3462
- }
3463
- function buildInspectorTree(instances, filter) {
3464
- const nodes = instances.map((instance) => {
3465
- const { r$, id, name, componentName } = instance;
3466
- return {
3467
- id,
3468
- label: name,
3469
- tags: buildNodeTags(r$, componentName),
3470
- children: buildRootChildrenNodes(r$, id)
3471
- };
3273
+ const $anyEdited = computed(() => {
3274
+ return Object.entries(regles).some(([_, regle]) => {
3275
+ return regle?.$anyEdited;
3276
+ });
3277
+ });
3278
+ const $instances = computed(() => {
3279
+ if (scoped) return Object.values(regles);
3280
+ else return regles;
3281
+ });
3282
+ function $reset(options) {
3283
+ Object.values(regles).forEach((regle) => {
3284
+ regle.$reset(options);
3285
+ });
3286
+ }
3287
+ function $touch() {
3288
+ Object.values(regles).forEach((regle) => {
3289
+ regle.$touch();
3290
+ });
3291
+ }
3292
+ function $extractDirtyFields(filterNullishValues = true) {
3293
+ return Object.values(regles).map((regle) => regle.$extractDirtyFields(filterNullishValues));
3294
+ }
3295
+ function $clearExternalErrors() {
3296
+ Object.values(regles).forEach((regle) => {
3297
+ regle.$clearExternalErrors();
3298
+ });
3299
+ }
3300
+ async function $validate(forceValues) {
3301
+ try {
3302
+ if (forceValues) $value.value = forceValues;
3303
+ const data = $value.value;
3304
+ return {
3305
+ valid: (await Promise.allSettled(Object.values(regles).map((regle) => {
3306
+ return regle.$validate();
3307
+ }))).every((value) => {
3308
+ if (value.status === "fulfilled") return value.value.valid === true;
3309
+ else return false;
3310
+ }),
3311
+ data,
3312
+ errors: $errors.value,
3313
+ issues: $issues.value
3314
+ };
3315
+ } catch {
3316
+ return {
3317
+ valid: false,
3318
+ data: $value.value,
3319
+ errors: $errors.value,
3320
+ issues: $issues.value
3321
+ };
3322
+ }
3323
+ }
3324
+ return reactive({
3325
+ ...!scoped && { $silentValue },
3326
+ $errors,
3327
+ $issues,
3328
+ $silentIssues,
3329
+ $silentErrors,
3330
+ $instances,
3331
+ $value,
3332
+ $dirty,
3333
+ $anyDirty,
3334
+ $invalid,
3335
+ $correct,
3336
+ $error,
3337
+ $pending,
3338
+ $ready,
3339
+ $edited,
3340
+ $anyEdited,
3341
+ $reset,
3342
+ $touch,
3343
+ $validate,
3344
+ $extractDirtyFields,
3345
+ $clearExternalErrors
3472
3346
  });
3473
- if (!filter || filter.trim() === "") return nodes;
3474
- return filterInspectorTree(nodes, filter);
3475
3347
  }
3476
- function filterInspectorTree(nodes, filter) {
3477
- const lowerFilter = filter.toLowerCase();
3478
- const filtered = [];
3479
- for (const node of nodes) {
3480
- const labelMatches = node.label.toLowerCase().includes(lowerFilter);
3481
- const tagMatches = node.tags?.some((tag) => tag.label.toLowerCase().includes(lowerFilter)) ?? false;
3482
- const filteredChildren = node.children ? filterInspectorTree(node.children, filter) : [];
3483
- if (labelMatches || tagMatches || filteredChildren.length > 0) filtered.push({
3484
- ...node,
3485
- children: filteredChildren.length > 0 ? filteredChildren : node.children
3486
- });
3348
+
3349
+ function createUseCollectScope(instances, options) {
3350
+ function useCollectScope$1(namespace) {
3351
+ const computedNamespace = computed(() => toValue(namespace));
3352
+ setEmptyNamespace();
3353
+ const r$ = ref(collectRegles(instances.value));
3354
+ const regle = reactive({ r$ });
3355
+ function setEmptyNamespace() {
3356
+ if (computedNamespace.value && !instances.value[computedNamespace.value]) instances.value[computedNamespace.value] = {};
3357
+ }
3358
+ watch(computedNamespace, setEmptyNamespace);
3359
+ watch(instances, (newInstances) => {
3360
+ r$.value = collectRegles(newInstances);
3361
+ }, { deep: true });
3362
+ function collectRegles(r$Instances) {
3363
+ if (computedNamespace.value) return mergeRegles(r$Instances[computedNamespace.value] ?? {}, !options.asRecord);
3364
+ else return mergeRegles(r$Instances["~~global"] ?? {}, !options.asRecord);
3365
+ }
3366
+ return { r$: regle.r$ };
3487
3367
  }
3488
- return filtered;
3368
+ return { useCollectScope: useCollectScope$1 };
3489
3369
  }
3490
3370
 
3491
- //#endregion
3492
- //#region src/devtools/devtools.ts
3493
- function createDevtools(app) {
3494
- setupDevtoolsPlugin({
3495
- id: "regle-devtools",
3496
- label: "Regle",
3497
- logo: "https://reglejs.dev/logo_main.png",
3498
- packageName: "@regle/core",
3499
- homepage: "https://reglejs.dev",
3500
- componentStateTypes: [],
3501
- app
3502
- }, (api) => {
3503
- api.addInspector({
3504
- id: INSPECTOR_IDS.INSPECTOR,
3505
- label: "Regle",
3506
- noSelectionText: "No instance selected",
3507
- icon: "rule",
3508
- treeFilterPlaceholder: "Filter",
3509
- stateFilterPlaceholder: "Filter validation status",
3510
- nodeActions: [
3511
- {
3512
- icon: "check_circle",
3513
- tooltip: "Validate (with `$validate`)",
3514
- action: (nodeId) => {
3515
- handleValidateAction(nodeId, api);
3516
- }
3517
- },
3518
- {
3519
- icon: "refresh",
3520
- tooltip: "Reset validation state (with `$reset`)",
3521
- action: (nodeId) => {
3522
- handleResetAction(nodeId, api);
3523
- }
3524
- },
3525
- {
3526
- icon: "restore",
3527
- tooltip: "Restore to initial state (with `$reset`)",
3528
- action: (nodeId) => {
3529
- handleResetAction(nodeId, api, true);
3530
- }
3531
- }
3532
- ]
3371
+ function createUseScopedRegleComposable(instances, customUseRegle) {
3372
+ const scopedUseRegle = customUseRegle ?? useRegle;
3373
+ const useScopedRegle$1 = ((state, rulesFactory, options) => {
3374
+ const { namespace, scopeKey: _scopeKey,...restOptions } = options ?? {};
3375
+ scopedUseRegle.__config ??= {};
3376
+ const computedNamespace = computed(() => toValue(namespace));
3377
+ const $id = ref(`${Object.keys(instances.value).length + 1}-${randomId()}`);
3378
+ const instanceName = computed(() => {
3379
+ return options?.scopeKey ?? `instance-${$id.value}`;
3533
3380
  });
3534
- setupInstanceWatchers(api);
3535
- api.on.getInspectorTree((payload) => {
3536
- if (payload.inspectorId === INSPECTOR_IDS.INSPECTOR) payload.rootNodes = buildInspectorTree(regleDevtoolsRegistry.getAll(), payload.filter);
3381
+ const { r$ } = scopedUseRegle(state, rulesFactory, restOptions);
3382
+ register();
3383
+ tryOnScopeDispose(dispose);
3384
+ watch(computedNamespace, (newName, oldName) => {
3385
+ dispose(oldName);
3386
+ register();
3537
3387
  });
3538
- api.on.getInspectorState((payload) => {
3539
- if (payload.inspectorId === INSPECTOR_IDS.INSPECTOR) {
3540
- const state = buildInspectorState(payload.nodeId, (id) => regleDevtoolsRegistry.get(id));
3541
- if (state) payload.state = state;
3388
+ if (getCurrentInstance()) onMounted(() => {
3389
+ const currentInstance = getCurrentInstance();
3390
+ if (typeof window !== "undefined" && currentInstance?.proxy?.$el?.parentElement) {
3391
+ if (document.documentElement && !document.documentElement.contains(currentInstance?.proxy?.$el?.parentElement)) dispose();
3542
3392
  }
3543
3393
  });
3544
- api.on.editInspectorState((payload) => {
3545
- if (payload.inspectorId === INSPECTOR_IDS.INSPECTOR) handleEditInspectorState(payload, api);
3546
- });
3394
+ function dispose(oldName) {
3395
+ const nameToClean = oldName ?? computedNamespace.value;
3396
+ if (nameToClean) {
3397
+ if (instances.value[nameToClean]) delete instances.value[nameToClean][instanceName.value];
3398
+ } else if (instances.value["~~global"][instanceName.value]) delete instances.value["~~global"][instanceName.value];
3399
+ }
3400
+ function register() {
3401
+ if (computedNamespace.value) {
3402
+ if (!instances.value[computedNamespace.value]) instances.value[computedNamespace.value] = {};
3403
+ instances.value[computedNamespace.value][instanceName.value] = r$;
3404
+ } else {
3405
+ if (!instances.value["~~global"]) instances.value["~~global"] = {};
3406
+ instances.value["~~global"][instanceName.value] = r$;
3407
+ }
3408
+ }
3409
+ return {
3410
+ r$,
3411
+ dispose,
3412
+ register
3413
+ };
3547
3414
  });
3415
+ return { useScopedRegle: useScopedRegle$1 };
3548
3416
  }
3549
- function setupInstanceWatchers(api) {
3550
- const watchedInstances = /* @__PURE__ */ new Set();
3551
- const setupWatchers = () => {
3552
- regleDevtoolsRegistry.getAll().forEach((instance) => {
3553
- const { r$, id } = instance;
3554
- if (watchedInstances.has(id)) return;
3555
- const stopHandle = watchRegleInstance(id, r$, () => {
3556
- api.sendInspectorState(INSPECTOR_IDS.INSPECTOR);
3557
- api.sendInspectorTree(INSPECTOR_IDS.INSPECTOR);
3558
- });
3559
- regleDevtoolsRegistry.addWatcher(id, stopHandle);
3560
- watchedInstances.add(id);
3561
- });
3417
+
3418
+ function createScopedUseRegle(options) {
3419
+ const instances = (options?.customStore ? () => {
3420
+ if (options.customStore) {
3421
+ if (!options.customStore?.value["~~global"]) options.customStore.value["~~global"] = {};
3422
+ else if (options.customStore?.value) options.customStore.value = { "~~global": {} };
3423
+ }
3424
+ return options.customStore;
3425
+ } : createGlobalState(() => {
3426
+ return ref({ "~~global": {} });
3427
+ }))();
3428
+ const { useScopedRegle: useScopedRegle$1 } = createUseScopedRegleComposable(instances, options?.customUseRegle);
3429
+ const { useCollectScope: useCollectScope$1 } = createUseCollectScope(instances, { asRecord: options?.asRecord });
3430
+ return {
3431
+ useScopedRegle: useScopedRegle$1,
3432
+ useCollectScope: useCollectScope$1
3562
3433
  };
3563
- setupWatchers();
3564
- regleDevtoolsRegistry.onInstancesChange(() => {
3565
- const currentIds = new Set(regleDevtoolsRegistry.getAll().map((i) => i.id));
3566
- for (const id of watchedInstances) if (!currentIds.has(id)) watchedInstances.delete(id);
3567
- api.sendInspectorTree(INSPECTOR_IDS.INSPECTOR);
3568
- setupWatchers();
3434
+ }
3435
+ const { useCollectScope, useScopedRegle } = createScopedUseRegle();
3436
+
3437
+ /**
3438
+ * Declare variations of state that depends on one value
3439
+ *
3440
+ * Autocomplete may not work here because of https://github.com/microsoft/TypeScript/issues/49547
3441
+ *
3442
+ * ```ts
3443
+ * // ⚠️ Use getter syntax for your rules () => {} or a computed one
3444
+ * const {r$} = useRegle(state, () => {
3445
+ * const variant = createVariant(state, 'type', [
3446
+ * {type: { literal: literal('EMAIL')}, email: { required, email }},
3447
+ * {type: { literal: literal('GITHUB')}, username: { required }},
3448
+ * {type: { required }},
3449
+ * ]);
3450
+ *
3451
+ * return {
3452
+ * ...variant.value,
3453
+ * };
3454
+ * })
3455
+ * ```
3456
+ */
3457
+ function createVariant(root, discriminantKey, variants) {
3458
+ const watchableRoot = computed(() => toValue(root)[discriminantKey]);
3459
+ return computed(() => {
3460
+ const selectedVariant = variants.find((variant) => {
3461
+ if (variant[discriminantKey] && "literal" in variant[discriminantKey]) {
3462
+ const literalRule = variant[discriminantKey]["literal"];
3463
+ if (isRuleDef(literalRule)) return unref(literalRule._params?.[0]) === watchableRoot.value;
3464
+ }
3465
+ });
3466
+ if (selectedVariant) return selectedVariant;
3467
+ else {
3468
+ const anyDiscriminantRules = variants.find((variant) => isObject(variant[discriminantKey]) && !Object.keys(variant[discriminantKey]).some((key) => key === "literal"));
3469
+ if (anyDiscriminantRules) return anyDiscriminantRules;
3470
+ else return {};
3471
+ }
3472
+ });
3473
+ }
3474
+ /**
3475
+ * Narrow a nested variant field to a discriminated value
3476
+ *
3477
+ * ```ts
3478
+ * if (narrowVariant(r$, 'type', 'EMAIL')) {
3479
+ * r$.email.$value = 'foo';
3480
+ * }
3481
+ * ```
3482
+ */
3483
+ function narrowVariant(root, discriminantKey, discriminantValue) {
3484
+ return isObject(root[discriminantKey]) && "$value" in root[discriminantKey] && root[discriminantKey]?.$value === discriminantValue;
3485
+ }
3486
+ function variantToRef(root, discriminantKey, discriminantValue, _options) {
3487
+ const fromRoot = isRef(root) ? toRef(root.value, "$fields") : toRef(root, "$fields");
3488
+ const returnedRef = ref();
3489
+ watch(fromRoot, async () => {
3490
+ await nextTick();
3491
+ if (narrowVariant(fromRoot.value, discriminantKey, discriminantValue)) returnedRef.value = toRef(root).value;
3492
+ else returnedRef.value = void 0;
3493
+ }, {
3494
+ immediate: true,
3495
+ flush: "pre"
3569
3496
  });
3497
+ return returnedRef;
3498
+ }
3499
+
3500
+ /**
3501
+ * Helper method to wrap an raw rules object
3502
+ *
3503
+ * Similar to:
3504
+ *
3505
+ * ```ts
3506
+ * const rules = {...} satisfies RegleUnknownRulesTree
3507
+ * ```
3508
+ */
3509
+ function defineRules(rules) {
3510
+ return rules;
3511
+ }
3512
+ /**
3513
+ * Refine a raw rules object to set rules that depends on the state values.
3514
+ *
3515
+ * @example
3516
+ *
3517
+ * ```ts
3518
+ * const rules = refineRules({
3519
+ * password: { required, type: type<string>() },
3520
+ * }, (state) => {
3521
+ * return {
3522
+ * confirmPassword: { required, sameAs: sameAs(() => state.value.password)}
3523
+ * }
3524
+ * })
3525
+ * ```
3526
+ */
3527
+ function refineRules(rules, refinement) {
3528
+ return (state) => merge({ ...rules }, refinement(state));
3570
3529
  }
3571
3530
 
3572
- //#endregion
3573
- //#region src/devtools/plugin.ts
3574
- const __USE_DEVTOOLS__ = process.env.NODE_ENV === "development";
3531
+ var version$1 = "1.11.0-beta.2";
3532
+
3575
3533
  const regleSymbol = Symbol("regle");
3534
+
3576
3535
  const RegleVuePlugin = { install(app) {
3577
- app.provide(regleSymbol, true);
3578
- if (typeof window !== "undefined" && __USE_DEVTOOLS__) createDevtools(app);
3536
+ app.provide(regleSymbol, version$1);
3537
+ if (typeof window !== "undefined" && true) createDevtools(app);
3579
3538
  } };
3580
3539
 
3581
- //#endregion
3582
- export { InternalRuleType, RegleVuePlugin, createRule, createScopedUseRegle, createVariant, defineRegleConfig, defineRules, extendRegleConfig, flatErrors, inferRules, mergeRegles, narrowVariant, refineRules, registerRegleInstance, unwrapRuleParameters, useCollectScope, useRegle, useRootStorage, useRules, useScopedRegle, variantToRef };
3540
+ export { InternalRuleType, RegleVuePlugin, createRule, createScopedUseRegle, createVariant, defineRegleConfig, defineRules, extendRegleConfig, flatErrors, inferRules, mergeRegles, narrowVariant, refineRules, unwrapRuleParameters, useCollectScope, useRegle, useRootStorage, useRules, useScopedRegle, variantToRef };