@structuralists/scaffolding 0.11.0 → 0.12.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.
- package/package.json +1 -1
- package/src/components/Json/JsonTable/JsonLeafNode.tsx +20 -17
- package/src/components/Json/JsonTable/JsonTable.stories.tsx +87 -0
- package/src/components/Json/JsonTable/index.tsx +13 -6
- package/src/components/Json/JsonTable/styles.module.css +20 -0
- package/src/components/Json/JsonTable/types.ts +3 -5
- package/src/forms/CLAUDE.md +104 -26
- package/src/forms/plan.md +104 -23
- package/src/forms/state/useFormState/FormDebugger.test.tsx +4 -3
- package/src/forms/state/useFormState/deriveErrors.test.ts +94 -0
- package/src/forms/state/useFormState/deriveErrors.ts +10 -10
- package/src/forms/state/useFormState/errorAt.test.ts +3 -3
- package/src/forms/state/useFormState/inspectable.test.ts +9 -9
- package/src/forms/state/useFormState/inspectable.ts +5 -7
- package/src/forms/state/useFormState/types.ts +2 -2
- package/src/forms/state/useFormState/useFormState.stories.tsx +32 -10
- package/src/forms/state/useFormState/useFormState.test-d.ts +436 -0
- package/src/forms/state/useFormState/useFormState.test.tsx +2 -2
- package/src/forms/state/validations/types.ts +77 -17
- package/src/forms/state/validations/walk.test.ts +159 -19
- package/src/forms/state/validations/walk.ts +86 -25
- package/tokens.css +55 -0
|
@@ -5,27 +5,26 @@ const pass = () => null;
|
|
|
5
5
|
const fail = (message: string) => () => message;
|
|
6
6
|
|
|
7
7
|
describe('validateEntry — single validator', () => {
|
|
8
|
-
test('a passing validator yields no
|
|
9
|
-
expect(validateEntry(pass, 'anything', ['a'])).
|
|
8
|
+
test('a passing validator yields no errors', () => {
|
|
9
|
+
expect(validateEntry(pass, 'anything', ['a'])).toEqual([]);
|
|
10
10
|
});
|
|
11
11
|
|
|
12
12
|
test('a failing validator yields its message at the given path', () => {
|
|
13
|
-
expect(validateEntry(fail('nope'), 'anything', ['a'])).toEqual(
|
|
14
|
-
path: ['a'],
|
|
15
|
-
|
|
16
|
-
});
|
|
13
|
+
expect(validateEntry(fail('nope'), 'anything', ['a'])).toEqual([
|
|
14
|
+
{ path: ['a'], error: 'nope' },
|
|
15
|
+
]);
|
|
17
16
|
});
|
|
18
17
|
|
|
19
18
|
test('the validator receives the field value', () => {
|
|
20
19
|
const spy = mock((val: string) => (val === 'x' ? null : 'expected x'));
|
|
21
|
-
expect(validateEntry(spy, 'x', ['a'])).
|
|
20
|
+
expect(validateEntry(spy, 'x', ['a'])).toEqual([]);
|
|
22
21
|
expect(spy).toHaveBeenCalledWith('x');
|
|
23
22
|
});
|
|
24
23
|
});
|
|
25
24
|
|
|
26
25
|
describe('validateEntry — validator arrays', () => {
|
|
27
|
-
test('all passing yields no
|
|
28
|
-
expect(validateEntry([pass, pass, pass], 'v', ['a'])).
|
|
26
|
+
test('all passing yields no errors', () => {
|
|
27
|
+
expect(validateEntry([pass, pass, pass], 'v', ['a'])).toEqual([]);
|
|
29
28
|
});
|
|
30
29
|
|
|
31
30
|
test('validators run in array order and the first error wins', () => {
|
|
@@ -34,7 +33,7 @@ describe('validateEntry — validator arrays', () => {
|
|
|
34
33
|
'v',
|
|
35
34
|
['a'],
|
|
36
35
|
);
|
|
37
|
-
expect(result).toEqual({ path: ['a'], error: 'second' });
|
|
36
|
+
expect(result).toEqual([{ path: ['a'], error: 'second' }]);
|
|
38
37
|
});
|
|
39
38
|
|
|
40
39
|
test('validators after the first failure are not called', () => {
|
|
@@ -46,20 +45,18 @@ describe('validateEntry — validator arrays', () => {
|
|
|
46
45
|
test('every validator before the failure sees the value', () => {
|
|
47
46
|
const first = mock(() => null);
|
|
48
47
|
const second = mock(() => null);
|
|
49
|
-
expect(validateEntry([first, second], 42, ['n'])).
|
|
48
|
+
expect(validateEntry([first, second], 42, ['n'])).toEqual([]);
|
|
50
49
|
expect(first).toHaveBeenCalledWith(42);
|
|
51
50
|
expect(second).toHaveBeenCalledWith(42);
|
|
52
51
|
});
|
|
53
52
|
|
|
54
53
|
test('an empty array passes', () => {
|
|
55
|
-
expect(validateEntry([], undefined, ['a'])).
|
|
54
|
+
expect(validateEntry([], undefined, ['a'])).toEqual([]);
|
|
56
55
|
});
|
|
57
56
|
});
|
|
58
57
|
|
|
59
58
|
describe('validateEntry — path accumulation', () => {
|
|
60
|
-
test('
|
|
61
|
-
// Phase 1 paths are single-key; the signature already speaks PathStep[]
|
|
62
|
-
// so the phases-2/3 {path, error}[] model needs no walk rewrite.
|
|
59
|
+
test('a leaf error carries the path it was given, verbatim', () => {
|
|
63
60
|
const result = validateEntry(fail('bad date'), undefined, [
|
|
64
61
|
'drivers',
|
|
65
62
|
3,
|
|
@@ -67,9 +64,152 @@ describe('validateEntry — path accumulation', () => {
|
|
|
67
64
|
0,
|
|
68
65
|
'date',
|
|
69
66
|
]);
|
|
70
|
-
expect(result).toEqual(
|
|
71
|
-
path: ['drivers', 3, 'incidents', 0, 'date'],
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
expect(result).toEqual([
|
|
68
|
+
{ path: ['drivers', 3, 'incidents', 0, 'date'], error: 'bad date' },
|
|
69
|
+
]);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('validateEntry — nested object specs', () => {
|
|
74
|
+
test('recurses into a nested spec, extending the path per key', () => {
|
|
75
|
+
const result = validateEntry(
|
|
76
|
+
{ city: fail('city required') },
|
|
77
|
+
{ city: undefined, line1: '1 Main St' },
|
|
78
|
+
['homeAddress'],
|
|
79
|
+
);
|
|
80
|
+
expect(result).toEqual([
|
|
81
|
+
{ path: ['homeAddress', 'city'], error: 'city required' },
|
|
82
|
+
]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('a constrained leaf validator receives the leaf value, not the section', () => {
|
|
86
|
+
const spy = mock((val: string) => (val === 'SF' ? null : 'not SF'));
|
|
87
|
+
expect(
|
|
88
|
+
validateEntry({ city: spy }, { city: 'SF' }, ['homeAddress']),
|
|
89
|
+
).toEqual([]);
|
|
90
|
+
expect(spy).toHaveBeenCalledWith('SF');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('specs nest to arbitrary depth, accumulating the full address', () => {
|
|
94
|
+
const result = validateEntry(
|
|
95
|
+
{ shipping: { address: { postalCode: fail('5 digits') } } },
|
|
96
|
+
{ shipping: { address: { postalCode: 'abc' } } },
|
|
97
|
+
['order'],
|
|
98
|
+
);
|
|
99
|
+
expect(result).toEqual([
|
|
100
|
+
{
|
|
101
|
+
path: ['order', 'shipping', 'address', 'postalCode'],
|
|
102
|
+
error: '5 digits',
|
|
103
|
+
},
|
|
104
|
+
]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('sibling results are independent: every failing node gets its own entry', () => {
|
|
108
|
+
const result = validateEntry(
|
|
109
|
+
{
|
|
110
|
+
city: fail('city required'),
|
|
111
|
+
state: pass,
|
|
112
|
+
postalCode: fail('postalCode required'),
|
|
113
|
+
},
|
|
114
|
+
{ city: undefined, state: 'CA', postalCode: undefined },
|
|
115
|
+
['homeAddress'],
|
|
116
|
+
);
|
|
117
|
+
expect(result).toEqual([
|
|
118
|
+
{ path: ['homeAddress', 'city'], error: 'city required' },
|
|
119
|
+
{ path: ['homeAddress', 'postalCode'], error: 'postalCode required' },
|
|
120
|
+
]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('a failing sibling does not stop validation of the others', () => {
|
|
124
|
+
const after = mock(() => null);
|
|
125
|
+
validateEntry(
|
|
126
|
+
{ a: fail('boom'), b: after },
|
|
127
|
+
{ a: 'x', b: 'y' },
|
|
128
|
+
['section'],
|
|
129
|
+
);
|
|
130
|
+
expect(after).toHaveBeenCalledWith('y');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('leaf and nested constraints mix inside one spec', () => {
|
|
134
|
+
const result = validateEntry(
|
|
135
|
+
{
|
|
136
|
+
name: [pass, fail('name too short')],
|
|
137
|
+
address: { city: fail('city required') },
|
|
138
|
+
},
|
|
139
|
+
{ name: 'x', address: { city: undefined } },
|
|
140
|
+
['applicant'],
|
|
141
|
+
);
|
|
142
|
+
expect(result).toEqual([
|
|
143
|
+
{ path: ['applicant', 'name'], error: 'name too short' },
|
|
144
|
+
{ path: ['applicant', 'address', 'city'], error: 'city required' },
|
|
145
|
+
]);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('a null child entry (possible from untyped JS) is skipped', () => {
|
|
149
|
+
const spec = { city: null } as unknown as {
|
|
150
|
+
city: (val: unknown) => string | null;
|
|
151
|
+
};
|
|
152
|
+
expect(validateEntry(spec, { city: undefined }, ['homeAddress'])).toEqual(
|
|
153
|
+
[],
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('validateEntry — absent sections are skipped', () => {
|
|
159
|
+
test('a nested spec on an undefined section validates nothing', () => {
|
|
160
|
+
// Decided with the phase-2 type spike: the type level refines only the
|
|
161
|
+
// present branch of a nullable section, so the runtime mirror is to
|
|
162
|
+
// skip. A "required section" is a leaf validator on the section field.
|
|
163
|
+
const never = mock(() => 'never reached');
|
|
164
|
+
expect(validateEntry({ city: never }, undefined, ['mailingAddress'])).toEqual([]);
|
|
165
|
+
expect(never).not.toHaveBeenCalled();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('a nested spec on a null section validates nothing', () => {
|
|
169
|
+
expect(validateEntry({ city: fail('x') }, null, ['mailingAddress'])).toEqual([]);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('a leaf validator on an absent section still runs (the "required section" form)', () => {
|
|
173
|
+
expect(validateEntry(fail('section required'), undefined, ['mailingAddress'])).toEqual([
|
|
174
|
+
{ path: ['mailingAddress'], error: 'section required' },
|
|
175
|
+
]);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('a nested spec on a non-object value (shape mismatch from untyped JS) is skipped', () => {
|
|
179
|
+
expect(validateEntry({ city: fail('x') }, 'not an object', ['homeAddress'])).toEqual([]);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('validateEntry — value-model disambiguation', () => {
|
|
184
|
+
test('an object value owning a key named `each` is walked as a nested spec', () => {
|
|
185
|
+
// The value directs interpretation, never the constraint's shape: this
|
|
186
|
+
// spec has a single key `each`, but the value is a plain object, so
|
|
187
|
+
// `each` is just a field like any other.
|
|
188
|
+
const result = validateEntry(
|
|
189
|
+
{ each: fail('audit each required') },
|
|
190
|
+
{ each: undefined, reviewedBy: 'wl' },
|
|
191
|
+
['audit'],
|
|
192
|
+
);
|
|
193
|
+
expect(result).toEqual([
|
|
194
|
+
{ path: ['audit', 'each'], error: 'audit each required' },
|
|
195
|
+
]);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe('validateEntry — list `each` specs (runtime pending, phase 3)', () => {
|
|
200
|
+
test('an `each` spec on a present list throws instead of silently not validating', () => {
|
|
201
|
+
expect(() =>
|
|
202
|
+
validateEntry(
|
|
203
|
+
{ each: { name: fail('name required') } },
|
|
204
|
+
[{ name: undefined }],
|
|
205
|
+
['drivers'],
|
|
206
|
+
),
|
|
207
|
+
).toThrow(/each.*not validated at runtime yet.*drivers/);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('an `each` spec on a null list is skipped — nothing to walk, matching phase-3 semantics', () => {
|
|
211
|
+
expect(
|
|
212
|
+
validateEntry({ each: { insurer: fail('x') } }, null, ['pastPolicies']),
|
|
213
|
+
).toEqual([]);
|
|
74
214
|
});
|
|
75
215
|
});
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { PathStep } from '../path/types';
|
|
2
2
|
|
|
3
3
|
// The runtime walk over a constraints object, kept separate from the hook so
|
|
4
|
-
// its semantics are unit-testable without React.
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
4
|
+
// its semantics are unit-testable without React. The grammar is recursive
|
|
5
|
+
// (nested object specs; list `each` specs arrive at runtime in plan phase 3),
|
|
6
|
+
// so the walk is a tree walk: it accumulates a `PathStep[]` address as it
|
|
7
|
+
// descends, and the hook's structured `{path, error}[]` error model
|
|
8
|
+
// (`FormErrors<T>`) consumes those addresses directly.
|
|
9
9
|
|
|
10
10
|
export type ValidationError = {
|
|
11
11
|
readonly path: readonly PathStep[];
|
|
@@ -21,33 +21,94 @@ type AnyFieldValidator = (val: never) => string | null;
|
|
|
21
21
|
// The walk's view of one entry in a `Validations<T>` object. This type is
|
|
22
22
|
// what lets the compiler police the walk's assumptions: the error derivation
|
|
23
23
|
// (`useFormState/deriveErrors.ts`) assigns `constraints[key]` to it WITHOUT
|
|
24
|
-
// a cast, so
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
|
|
24
|
+
// a cast, so if the grammar grows a form the walk doesn't understand, that
|
|
25
|
+
// assignment stops compiling and the walk must learn the new form — instead
|
|
26
|
+
// of a stale walk misinterpreting it at runtime. Both structural grammar
|
|
27
|
+
// forms (a nested `Validations`, a `{ each: … }` list constraint) look the
|
|
28
|
+
// same from here — an object of entries — because disambiguating them is the
|
|
29
|
+
// walk's job, done against the VALUE at the path, never the constraint's
|
|
30
|
+
// shape (see "Disambiguation" in plan.md).
|
|
31
|
+
export type ConstraintEntry =
|
|
29
32
|
| AnyFieldValidator
|
|
30
|
-
| readonly AnyFieldValidator[]
|
|
33
|
+
| readonly AnyFieldValidator[]
|
|
34
|
+
| ConstraintSpec;
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
type ConstraintSpec = {
|
|
37
|
+
readonly [key: string]: ConstraintEntry | undefined;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Runs one constraint entry against the field value found at `path`,
|
|
41
|
+
// returning every failure in the entry's subtree (a structural spec can
|
|
42
|
+
// produce one entry per failing constrained node; sibling results are
|
|
43
|
+
// independent).
|
|
44
|
+
//
|
|
45
|
+
// Leaf semantics for arrays (identical to `allOf`): validators run in array
|
|
34
46
|
// order, first error wins — later validators are not called once one fails.
|
|
35
47
|
// An empty array passes, mirroring its refinement (`Exclude<F, never>`).
|
|
48
|
+
//
|
|
49
|
+
// Structural semantics — interpretation is directed by the value model:
|
|
50
|
+
// - value is an array ⇒ the spec is a `{ each: … }` list constraint.
|
|
51
|
+
// Runtime `each` walking is plan phase 3; until it lands the walk THROWS
|
|
52
|
+
// rather than silently not validating.
|
|
53
|
+
// - value is an object ⇒ the spec is a nested `Validations`: recurse into
|
|
54
|
+
// each constrained key, extending the path. (An object field owning a key
|
|
55
|
+
// literally named `each` lands here — the value directs, so its spec is a
|
|
56
|
+
// nested spec like any other.)
|
|
57
|
+
// - value is absent (null/undefined) ⇒ SKIP: a nested spec on an absent
|
|
58
|
+
// section validates nothing (decided with the type spike — the type level
|
|
59
|
+
// refines only the present branch of a nullable section, and this is the
|
|
60
|
+
// honest runtime mirror). A "required section" is a LEAF validator on the
|
|
61
|
+
// section field instead. A non-object value (unreachable through the typed
|
|
62
|
+
// grammar) has nothing to walk either, mirroring read()'s dead-step
|
|
63
|
+
// semantics.
|
|
64
|
+
// Predicate (not inline checks) because `Array.isArray`'s `arg is any[]`
|
|
65
|
+
// guard cannot remove a READONLY array member from its false branch — the
|
|
66
|
+
// structural-spec arm below needs that removal to type `entry[key]`.
|
|
67
|
+
const isLeafEntry = (
|
|
68
|
+
entry: ConstraintEntry,
|
|
69
|
+
): entry is AnyFieldValidator | readonly AnyFieldValidator[] =>
|
|
70
|
+
typeof entry === 'function' || Array.isArray(entry);
|
|
71
|
+
|
|
36
72
|
export const validateEntry = (
|
|
37
|
-
entry:
|
|
73
|
+
entry: ConstraintEntry,
|
|
38
74
|
value: unknown,
|
|
39
75
|
path: readonly PathStep[],
|
|
40
|
-
): ValidationError
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
76
|
+
): ValidationError[] => {
|
|
77
|
+
if (isLeafEntry(entry)) {
|
|
78
|
+
const validators = typeof entry === 'function' ? [entry] : entry;
|
|
79
|
+
|
|
80
|
+
for (const validator of validators) {
|
|
81
|
+
// Safe: the constraints object was type-checked against the form type
|
|
82
|
+
// where it was built, so this validator accepts the value at `path`;
|
|
83
|
+
// the walk just can't see that correlation. Same honest contravariant
|
|
84
|
+
// widening as `allOf`'s part-call.
|
|
85
|
+
const error = (validator as (val: unknown) => string | null)(value);
|
|
86
|
+
if (error != null) return [{ path, error }];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (value == null || typeof value !== 'object') return [];
|
|
93
|
+
|
|
94
|
+
if (Array.isArray(value)) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`list 'each' constraints are not validated at runtime yet (forms plan ` +
|
|
97
|
+
`phase 3) — constraint at path [${path.join(', ')}]`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const errors: ValidationError[] = [];
|
|
102
|
+
for (const key of Object.keys(entry)) {
|
|
103
|
+
const child = entry[key];
|
|
104
|
+
if (child == null) continue;
|
|
105
|
+
errors.push(
|
|
106
|
+
...validateEntry(child, (value as Record<string, unknown>)[key], [
|
|
107
|
+
...path,
|
|
108
|
+
key,
|
|
109
|
+
]),
|
|
110
|
+
);
|
|
50
111
|
}
|
|
51
112
|
|
|
52
|
-
return
|
|
113
|
+
return errors;
|
|
53
114
|
};
|
package/tokens.css
CHANGED
|
@@ -43,6 +43,13 @@
|
|
|
43
43
|
shadow-soft — gentler lift (hover elevation, cards)
|
|
44
44
|
backdrop — modal overlay tint behind dialogs
|
|
45
45
|
|
|
46
|
+
JSON syntax (Chrome-inspector-style value coloring in JsonTable)
|
|
47
|
+
json-key — object keys / array indices (low salience)
|
|
48
|
+
json-string — string leaves (rendered with quotes)
|
|
49
|
+
json-number — number leaves
|
|
50
|
+
json-boolean — boolean leaves
|
|
51
|
+
json-nil — null / undefined leaves
|
|
52
|
+
|
|
46
53
|
These values are scaffolds, not final design. Revise deliberately.
|
|
47
54
|
--------------------------------------------------------------------------- */
|
|
48
55
|
|
|
@@ -112,6 +119,12 @@
|
|
|
112
119
|
--ui-shadow: rgba(0, 0, 0, 0.15);
|
|
113
120
|
--ui-shadow-soft: rgba(0, 0, 0, 0.08);
|
|
114
121
|
--ui-backdrop: rgba(30, 28, 25, 0.35);
|
|
122
|
+
|
|
123
|
+
--ui-json-key: #6c6761;
|
|
124
|
+
--ui-json-string: #a05419;
|
|
125
|
+
--ui-json-number: #4966a9;
|
|
126
|
+
--ui-json-boolean: #8a35a0;
|
|
127
|
+
--ui-json-nil: #a74c4c;
|
|
115
128
|
}
|
|
116
129
|
|
|
117
130
|
/* System preference: dark → fall back to dark-warm when no data-theme is set.
|
|
@@ -140,6 +153,12 @@
|
|
|
140
153
|
--ui-shadow: rgba(0, 0, 0, 0.5);
|
|
141
154
|
--ui-shadow-soft: rgba(0, 0, 0, 0.3);
|
|
142
155
|
--ui-backdrop: rgba(0, 0, 0, 0.55);
|
|
156
|
+
|
|
157
|
+
--ui-json-key: #a89f90;
|
|
158
|
+
--ui-json-string: #e6a268;
|
|
159
|
+
--ui-json-number: #8aa5d8;
|
|
160
|
+
--ui-json-boolean: #c98ad5;
|
|
161
|
+
--ui-json-nil: #f0a39e;
|
|
143
162
|
}
|
|
144
163
|
}
|
|
145
164
|
|
|
@@ -170,6 +189,12 @@
|
|
|
170
189
|
--ui-shadow: rgba(0, 0, 0, 0.15);
|
|
171
190
|
--ui-shadow-soft: rgba(0, 0, 0, 0.08);
|
|
172
191
|
--ui-backdrop: rgba(30, 28, 25, 0.35);
|
|
192
|
+
|
|
193
|
+
--ui-json-key: #6c6761;
|
|
194
|
+
--ui-json-string: #a05419;
|
|
195
|
+
--ui-json-number: #4966a9;
|
|
196
|
+
--ui-json-boolean: #8a35a0;
|
|
197
|
+
--ui-json-nil: #a74c4c;
|
|
173
198
|
}
|
|
174
199
|
|
|
175
200
|
[data-theme='light-paper'] {
|
|
@@ -195,6 +220,12 @@
|
|
|
195
220
|
--ui-shadow: rgba(0, 0, 0, 0.12);
|
|
196
221
|
--ui-shadow-soft: rgba(0, 0, 0, 0.06);
|
|
197
222
|
--ui-backdrop: rgba(20, 20, 20, 0.35);
|
|
223
|
+
|
|
224
|
+
--ui-json-key: #636360;
|
|
225
|
+
--ui-json-string: #9b4f16;
|
|
226
|
+
--ui-json-number: #4461a3;
|
|
227
|
+
--ui-json-boolean: #8a35a0;
|
|
228
|
+
--ui-json-nil: #a14747;
|
|
198
229
|
}
|
|
199
230
|
|
|
200
231
|
[data-theme='light-sepia'] {
|
|
@@ -220,6 +251,12 @@
|
|
|
220
251
|
--ui-shadow: rgba(70, 50, 20, 0.18);
|
|
221
252
|
--ui-shadow-soft: rgba(70, 50, 20, 0.09);
|
|
222
253
|
--ui-backdrop: rgba(50, 35, 15, 0.4);
|
|
254
|
+
|
|
255
|
+
--ui-json-key: #6a5e45;
|
|
256
|
+
--ui-json-string: #954a10;
|
|
257
|
+
--ui-json-number: #3a5da0;
|
|
258
|
+
--ui-json-boolean: #7a2a8a;
|
|
259
|
+
--ui-json-nil: #9a4444;
|
|
223
260
|
}
|
|
224
261
|
|
|
225
262
|
[data-theme='dark-warm'] {
|
|
@@ -245,6 +282,12 @@
|
|
|
245
282
|
--ui-shadow: rgba(0, 0, 0, 0.5);
|
|
246
283
|
--ui-shadow-soft: rgba(0, 0, 0, 0.3);
|
|
247
284
|
--ui-backdrop: rgba(0, 0, 0, 0.55);
|
|
285
|
+
|
|
286
|
+
--ui-json-key: #a89f90;
|
|
287
|
+
--ui-json-string: #e6a268;
|
|
288
|
+
--ui-json-number: #8aa5d8;
|
|
289
|
+
--ui-json-boolean: #c98ad5;
|
|
290
|
+
--ui-json-nil: #f0a39e;
|
|
248
291
|
}
|
|
249
292
|
|
|
250
293
|
[data-theme='dark-neutral'] {
|
|
@@ -270,6 +313,12 @@
|
|
|
270
313
|
--ui-shadow: rgba(0, 0, 0, 0.5);
|
|
271
314
|
--ui-shadow-soft: rgba(0, 0, 0, 0.3);
|
|
272
315
|
--ui-backdrop: rgba(0, 0, 0, 0.6);
|
|
316
|
+
|
|
317
|
+
--ui-json-key: #a0a0a0;
|
|
318
|
+
--ui-json-string: #e29964;
|
|
319
|
+
--ui-json-number: #8ab0e0;
|
|
320
|
+
--ui-json-boolean: #c990d6;
|
|
321
|
+
--ui-json-nil: #ea9a9a;
|
|
273
322
|
}
|
|
274
323
|
|
|
275
324
|
[data-theme='dark-dimmed'] {
|
|
@@ -295,6 +344,12 @@
|
|
|
295
344
|
--ui-shadow: rgba(0, 0, 0, 0.4);
|
|
296
345
|
--ui-shadow-soft: rgba(0, 0, 0, 0.25);
|
|
297
346
|
--ui-backdrop: rgba(0, 0, 0, 0.55);
|
|
347
|
+
|
|
348
|
+
--ui-json-key: #8a91a0;
|
|
349
|
+
--ui-json-string: #d99970;
|
|
350
|
+
--ui-json-number: #8ab0e0;
|
|
351
|
+
--ui-json-boolean: #c08adc;
|
|
352
|
+
--ui-json-nil: #e89898;
|
|
298
353
|
}
|
|
299
354
|
|
|
300
355
|
/* ---------------------------------------------------------------------------
|