@globalbrain/sefirot 4.48.1 → 4.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,19 +1,83 @@
1
1
  export type Rule =
2
2
  | MaxLengthRule
3
+ | MinLengthRule
4
+ | MinValueRule
5
+ | MaxValueRule
3
6
  | RequiredRule
4
7
  | UniqueRule
8
+ | EmailRule
9
+ | UrlRule
10
+ | DecimalRule
11
+ | DecimalOrHyphenRule
12
+ | PositiveIntegerRule
13
+ | NegativeIntegerRule
14
+ | ZeroOrPositiveIntegerRule
15
+ | ZeroOrNegativeIntegerRule
16
+ | CheckedRule
5
17
  | SlackChannelLinkRule
6
18
  | SlackChannelNameRule
7
19
  | BeforeRule
8
20
  | BeforeOrEqualRule
9
21
  | AfterRule
10
22
  | AfterOrEqualRule
23
+ | EachRule
11
24
 
12
25
  export interface MaxLengthRule {
13
26
  type: 'max_length'
14
27
  length: number
15
28
  }
16
29
 
30
+ export interface MinLengthRule {
31
+ type: 'min_length'
32
+ length: number
33
+ }
34
+
35
+ export interface MinValueRule {
36
+ type: 'min_value'
37
+ value: number
38
+ }
39
+
40
+ export interface MaxValueRule {
41
+ type: 'max_value'
42
+ value: number
43
+ }
44
+
45
+ export interface EmailRule {
46
+ type: 'email'
47
+ }
48
+
49
+ export interface UrlRule {
50
+ type: 'url'
51
+ }
52
+
53
+ export interface DecimalRule {
54
+ type: 'decimal'
55
+ }
56
+
57
+ export interface DecimalOrHyphenRule {
58
+ type: 'decimal_or_hyphen'
59
+ }
60
+
61
+ export interface PositiveIntegerRule {
62
+ type: 'positive_integer'
63
+ }
64
+
65
+ export interface NegativeIntegerRule {
66
+ type: 'negative_integer'
67
+ }
68
+
69
+ export interface ZeroOrPositiveIntegerRule {
70
+ type: 'zero_or_positive_integer'
71
+ }
72
+
73
+ export interface ZeroOrNegativeIntegerRule {
74
+ type: 'zero_or_negative_integer'
75
+ }
76
+
77
+ export interface CheckedRule {
78
+ type: 'checked'
79
+ }
80
+
17
81
  export interface RequiredRule {
18
82
  type: 'required'
19
83
  }
@@ -50,3 +114,12 @@ export interface AfterOrEqualRule {
50
114
  type: 'after_or_equal'
51
115
  date: string
52
116
  }
117
+
118
+ /**
119
+ * Applies the nested `rules` to every element of an array value, mirroring
120
+ * Laravel's `field.*` wildcard on the backend.
121
+ */
122
+ export interface EachRule {
123
+ type: 'each'
124
+ rules: Rule[]
125
+ }
@@ -5,30 +5,90 @@ import {
5
5
  afterOrEqual,
6
6
  before,
7
7
  beforeOrEqual,
8
+ checked,
9
+ decimal,
10
+ decimalOrHyphen,
11
+ email,
8
12
  maxLength,
13
+ maxValue,
14
+ minLength,
15
+ minValue,
16
+ negativeInteger,
17
+ positiveInteger,
9
18
  required,
19
+ rule,
10
20
  slackChannelLink,
11
- slackChannelName
21
+ slackChannelName,
22
+ url,
23
+ zeroOrNegativeInteger,
24
+ zeroOrPositiveInteger
12
25
  } from '../../../validation/rules'
13
- import { type Rule } from '../Rule'
26
+ import { type EachRule, type Rule } from '../Rule'
14
27
 
15
28
  /**
16
29
  * Maps field rules to vuelidate validation rules.
17
30
  */
18
31
  export function map(rules: Rule[]): ValidationArgs {
19
- return rules.reduce((carry: ValidationArgs<any>, rule: Rule) => {
32
+ const carry = rules.reduce((carry: ValidationArgs<any>, rule: Rule) => {
33
+ // `each` rules are handled separately below so that multiple of them on the
34
+ // same field compose rather than overwrite.
35
+ if (rule.type === 'each') {
36
+ return carry
37
+ }
20
38
  const mapped = mapRule(rule)
21
39
  if (mapped) {
22
40
  carry[rule.type] = mapped
23
41
  }
24
42
  return carry
25
43
  }, {})
44
+
45
+ // `each` validates every element of an array value rather than the value
46
+ // itself, so it maps to a single per-element validator (see `mapEach`). If a
47
+ // field carries more than one `each` rule, merge their child rules so every
48
+ // wildcard rule runs — the backend accumulates them the same way. A single
49
+ // `each` is the common case.
50
+ const eachRules = rules.filter((rule): rule is EachRule => rule.type === 'each')
51
+ if (eachRules.length) {
52
+ carry.each = mapEach({ type: 'each', rules: eachRules.flatMap((rule) => rule.rules) })
53
+ }
54
+
55
+ return carry
56
+ }
57
+
58
+ /**
59
+ * Builds a validator that applies an `each` rule's nested rules to every
60
+ * element of an array value. vuelidate's `forEach` only handles arrays of
61
+ * objects, so for the scalar arrays Lens fields hold (multi-select values,
62
+ * related ids) we run each element through the mapped child validators
63
+ * directly. Mirrors the backend, which expands the same rules onto Laravel's
64
+ * `key.*` wildcard. A non-array value fails; an empty/absent value is left to
65
+ * `required` (the rule is optional on its own).
66
+ */
67
+ function mapEach(eachRule: EachRule): ValidationRuleWithParams {
68
+ // Map each child rule straight to a validator rather than through the
69
+ // type-keyed `map()` object, so two child rules of the same family are both
70
+ // kept (the backend's `key.*` expansion is additive and de-dupes nothing).
71
+ // Server-only children (e.g. `unique`) map to null and are dropped.
72
+ const childValidators = eachRule.rules
73
+ .map((r) => (r.type === 'each' ? mapEach(r) : mapRule(r)))
74
+ .filter((v): v is ValidationRuleWithParams => v !== null)
75
+
76
+ return rule((value) =>
77
+ Array.isArray(value)
78
+ && value.every((item) => childValidators.every((v) => v.$validator(item, {}, {}) === true))
79
+ )
26
80
  }
27
81
 
28
- function mapRule(rule: Rule): ValidationRuleWithParams | null {
82
+ function mapRule(rule: Exclude<Rule, EachRule>): ValidationRuleWithParams | null {
29
83
  switch (rule.type) {
30
84
  case 'max_length':
31
85
  return maxLength(rule.length)
86
+ case 'min_length':
87
+ return minLength(rule.length)
88
+ case 'min_value':
89
+ return minValue(rule.value)
90
+ case 'max_value':
91
+ return maxValue(rule.value)
32
92
  case 'required':
33
93
  return required()
34
94
  case 'unique':
@@ -36,6 +96,24 @@ function mapRule(rule: Rule): ValidationRuleWithParams | null {
36
96
  // database). The backend enforces it and returns a 422 with field
37
97
  // errors, so there is no client-side equivalent to apply here.
38
98
  return null
99
+ case 'email':
100
+ return email()
101
+ case 'url':
102
+ return url()
103
+ case 'decimal':
104
+ return decimal()
105
+ case 'decimal_or_hyphen':
106
+ return decimalOrHyphen()
107
+ case 'positive_integer':
108
+ return positiveInteger()
109
+ case 'negative_integer':
110
+ return negativeInteger()
111
+ case 'zero_or_positive_integer':
112
+ return zeroOrPositiveInteger()
113
+ case 'zero_or_negative_integer':
114
+ return zeroOrNegativeInteger()
115
+ case 'checked':
116
+ return checked()
39
117
  case 'slack_channel_link':
40
118
  return slackChannelLink()
41
119
  case 'slack_channel_name':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@globalbrain/sefirot",
3
- "version": "4.48.1",
3
+ "version": "4.49.0",
4
4
  "description": "Vue Components for Global Brain Design System.",
5
5
  "keywords": [
6
6
  "components",