vanilla-jui 1.7.1 → 1.7.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.
@@ -0,0 +1,97 @@
1
+ import { isPlainObject } from './object.ts';
2
+
3
+ export type MergeStrategy =
4
+ | 'replace'
5
+ | 'shallow'
6
+ | 'deep'
7
+ | ((
8
+ defaultValue: unknown,
9
+ inputValue: unknown,
10
+ context: MergeContext
11
+ ) => unknown);
12
+
13
+ export interface MergeContext {
14
+ key: string;
15
+ path: string;
16
+ }
17
+
18
+ type LooseRecord = Record<string, unknown>;
19
+
20
+ const UNSAFE_KEYS = new Set(['__proto__', 'prototype', 'constructor']);
21
+
22
+ function isSafeKey(key: string): boolean {
23
+ return !UNSAFE_KEYS.has(key);
24
+ }
25
+
26
+ export function cloneConfigValue<T>(value: T): T {
27
+ if (Array.isArray(value)) return value.map(cloneConfigValue) as T;
28
+ if (!isPlainObject(value)) return value;
29
+
30
+ const result: LooseRecord = {};
31
+ for (const [key, child] of Object.entries(value as LooseRecord)) {
32
+ if (!isSafeKey(key)) continue;
33
+ result[key] = cloneConfigValue(child);
34
+ }
35
+ return result as T;
36
+ }
37
+
38
+ export function mergeShallowConfig(
39
+ defaultValue: unknown,
40
+ inputValue: unknown
41
+ ): unknown {
42
+ if (!isPlainObject(defaultValue) || !isPlainObject(inputValue)) {
43
+ return cloneConfigValue(inputValue);
44
+ }
45
+
46
+ const result = cloneConfigValue(defaultValue) as LooseRecord;
47
+ for (const [key, value] of Object.entries(inputValue as LooseRecord)) {
48
+ if (!isSafeKey(key)) continue;
49
+ result[key] = cloneConfigValue(value);
50
+ }
51
+ return result;
52
+ }
53
+
54
+ export function mergeDeepConfig(
55
+ defaultValue: unknown,
56
+ inputValue: unknown
57
+ ): unknown {
58
+ if (!isPlainObject(defaultValue) || !isPlainObject(inputValue)) {
59
+ return cloneConfigValue(inputValue);
60
+ }
61
+
62
+ const result = cloneConfigValue(defaultValue) as LooseRecord;
63
+ for (const [key, value] of Object.entries(inputValue as LooseRecord)) {
64
+ if (!isSafeKey(key)) continue;
65
+ const current = result[key];
66
+ result[key] =
67
+ isPlainObject(current) && isPlainObject(value)
68
+ ? mergeDeepConfig(current, value)
69
+ : cloneConfigValue(value);
70
+ }
71
+ return result;
72
+ }
73
+
74
+ export function mergeConfigValue(
75
+ defaultValue: unknown,
76
+ inputValue: unknown,
77
+ strategy: MergeStrategy | undefined,
78
+ context: MergeContext
79
+ ): unknown {
80
+ if (typeof strategy === 'function') {
81
+ return strategy(
82
+ cloneConfigValue(defaultValue),
83
+ cloneConfigValue(inputValue),
84
+ context
85
+ );
86
+ }
87
+
88
+ if (strategy === 'shallow') {
89
+ return mergeShallowConfig(defaultValue, inputValue);
90
+ }
91
+
92
+ if (strategy === 'deep') {
93
+ return mergeDeepConfig(defaultValue, inputValue);
94
+ }
95
+
96
+ return cloneConfigValue(inputValue);
97
+ }
@@ -55,14 +55,7 @@ export type ValidateCondition =
55
55
  message?: string;
56
56
  };
57
57
 
58
- export interface NormalizeContext<TInput extends LooseRecord = LooseRecord> {
59
- key: string;
60
- input: TInput;
61
- options: LooseRecord;
62
- schema: ResolveSchema<TInput>;
63
- }
64
-
65
- export interface ParamRule<TInput extends LooseRecord = LooseRecord> {
58
+ export interface ParamRule<_TInput extends LooseRecord = LooseRecord> {
66
59
  type?: TypeRule;
67
60
  types?: TypeRule;
68
61
  required?: boolean;
@@ -78,13 +71,11 @@ export interface ParamRule<TInput extends LooseRecord = LooseRecord> {
78
71
  lessThan?: number;
79
72
  plain?: boolean;
80
73
  items?: ParamRuleInput;
81
- shape?: ResolveSchema;
74
+ shape?: ParamShapeSchema;
82
75
  conditions?: ValidateCondition | readonly ValidateCondition[];
83
76
  validate?: (value: unknown) => boolean;
84
77
  message?: string;
85
- normalize?: (value: unknown, context: NormalizeContext<TInput>) => unknown;
86
78
  default?: unknown;
87
- factory?: boolean;
88
79
  [key: string]: unknown;
89
80
  }
90
81
 
@@ -92,21 +83,11 @@ export type ParamRuleInput<TInput extends LooseRecord = LooseRecord> =
92
83
  | TypeRule
93
84
  | ParamRule<TInput>;
94
85
 
95
- export type ResolveSchema<TInput extends LooseRecord = LooseRecord> = Record<
86
+ export type ParamShapeSchema<TInput extends LooseRecord = LooseRecord> = Record<
96
87
  string,
97
88
  ParamRuleInput<TInput>
98
89
  >;
99
90
 
100
- export type ResolvedProps<TSchema extends object> = LooseRecord & {
101
- [Key in keyof TSchema]: unknown;
102
- };
103
-
104
- function cloneDefault<T>(value: T): T {
105
- if (Array.isArray(value)) return value.slice() as T;
106
- if (isPlainObject(value)) return { ...(value as LooseRecord) } as T;
107
- return value;
108
- }
109
-
110
91
  function normalizeRule<TInput extends LooseRecord = LooseRecord>(
111
92
  rule: ParamRuleInput<TInput> = {}
112
93
  ): ParamRule<TInput> {
@@ -332,66 +313,6 @@ export function validateParam<TInput extends LooseRecord = LooseRecord>(
332
313
  return value;
333
314
  }
334
315
 
335
- export function resolveProps<
336
- TInput extends LooseRecord,
337
- TSchema extends ResolveSchema<TInput>,
338
- >(
339
- input: TInput | null | undefined = {} as TInput,
340
- schema: TSchema = {} as TSchema,
341
- namespace = 'Options'
342
- ): TInput & ResolvedProps<TSchema> {
343
- const source = input == null ? {} : input;
344
- if (typeof source !== 'object' || Array.isArray(source)) {
345
- throw new Error(`${namespace} expects object.`);
346
- }
347
-
348
- const sourceRecord = source as TInput;
349
- const resolved: LooseRecord = { ...sourceRecord };
350
- const entries = Object.entries(schema || {}) as Array<
351
- [string, ParamRuleInput<TInput>]
352
- >;
353
-
354
- for (const [key, rawRule] of entries) {
355
- const rule = normalizeRule<TInput>(rawRule);
356
- resolved[key] = Object.hasOwn(sourceRecord, key)
357
- ? sourceRecord[key]
358
- : resolveDefault(rule);
359
- }
360
-
361
- for (const [key, rawRule] of entries) {
362
- const rule = normalizeRule<TInput>(rawRule);
363
- if (typeof rule.normalize === 'function') {
364
- resolved[key] = rule.normalize(resolved[key], {
365
- key,
366
- input: sourceRecord,
367
- options: resolved,
368
- schema,
369
- });
370
- }
371
- }
372
-
373
- for (const [key, rule] of entries) {
374
- validateParam(key, resolved[key], rule, namespace);
375
- }
376
-
377
- return resolved as TInput & ResolvedProps<TSchema>;
378
- }
379
-
380
- interface DefaultRule {
381
- default?: unknown;
382
- factory?: boolean;
383
- }
384
-
385
- function resolveDefault(rule: DefaultRule): unknown {
386
- if (!Object.hasOwn(rule, 'default')) return undefined;
387
- const defaultValue = rule.default;
388
- const value =
389
- rule.factory && typeof defaultValue === 'function'
390
- ? defaultValue()
391
- : defaultValue;
392
- return cloneDefault(value);
393
- }
394
-
395
316
  /**
396
317
  * 判断是否为普通可构造函数(排除箭头函数和类)。
397
318
  * @param fn - 需要判断的值
@@ -7,12 +7,8 @@ import {
7
7
  resolveElement,
8
8
  } from '../utilities/dom.ts';
9
9
  import { createEventManager } from '../utilities/events.ts';
10
- import { isPlainObject } from '../utilities/object.ts';
11
- import {
12
- type ResolveSchema,
13
- resolveProps,
14
- validateParam,
15
- } from '../utilities/types.ts';
10
+ import { type ConfigSchema, resolveConfig } from '../utilities/config.ts';
11
+ import { validateParam } from '../utilities/types.ts';
16
12
 
17
13
  type ValidatorElement =
18
14
  | HTMLInputElement
@@ -99,21 +95,14 @@ const VALIDATOR_PROPS_SCHEMA = {
99
95
  vanilla: { default: false, type: 'boolean' },
100
96
  className: {
101
97
  default: DEFAULT_CLASS_NAMES,
102
- type: 'object',
103
- normalize: (value: unknown) => resolveClassNames(value),
98
+ type: 'plainObject',
99
+ merge: 'shallow',
104
100
  },
105
101
  onSubmit: { default: null, types: ['function', 'null'] },
106
- } satisfies ResolveSchema<ValidatorProps>;
107
-
108
- function resolveClassNames(value: unknown): ValidatorClassNames {
109
- return {
110
- ...DEFAULT_CLASS_NAMES,
111
- ...(isPlainObject(value) ? (value as ValidatorClassNameConfig) : {}),
112
- } as ValidatorClassNames;
113
- }
102
+ } satisfies ConfigSchema<ValidatorProps>;
114
103
 
115
104
  function normalizeProps(input: ValidatorProps): ResolvedValidatorProps {
116
- const props = resolveProps(input, VALIDATOR_PROPS_SCHEMA, 'Validator.props');
105
+ const props = resolveConfig(input, VALIDATOR_PROPS_SCHEMA, 'Validator.props');
117
106
  return {
118
107
  rules: props.rules as Record<string, ValidatorRule>,
119
108
  messages: props.messages as ValidatorMessageMap,