@native-form/core 0.1.1 → 0.1.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.
- package/README.md +6 -3
- package/dist/index.cjs +32 -8
- package/dist/index.d.cts +23 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.js +32 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -166,13 +166,16 @@ When instantiating `new Runtime(options)`, the following options are available:
|
|
|
166
166
|
|
|
167
167
|
| Option | Type | Default | Description |
|
|
168
168
|
| :--- | :--- | :--- | :--- |
|
|
169
|
-
| `mode` | `"onSubmit" \| "onBlur" \| "onChange" \| "onTouched" \| "all"` | `"onSubmit"` |
|
|
170
|
-
| `
|
|
169
|
+
| `mode` | `"onSubmit" \| "onBlur" \| "onChange" \| "onTouched" \| "all"` | `"onSubmit"` | Initial validation trigger strategy. |
|
|
170
|
+
| `reValidateMode` | `"onChange" \| "onBlur" \| "onSubmit"` | `"onChange"` | Re-validation strategy for fields with active errors. Automatically re-validates discrete controls (`checkbox`, `radio`, `select`) on change. |
|
|
171
|
+
| `renderDomErrors` | `boolean` | `true` | Whether the runtime automatically injects DOM `<div data-native-form-error="true">` elements next to invalid inputs. Set to `false` when rendering custom UI errors (defaults to `false` in `@native-form/react`). |
|
|
172
|
+
| `serializationMode` | `"native" \| "typed"` | `"native"` | Whether values are serialized as raw strings/FormData or coerced typed values (numbers, booleans, arrays). |
|
|
171
173
|
| `nesting` | `"flat" \| "nested"` | `"flat"` | Whether dot/bracket notation paths are converted into deep nested objects (e.g. `user.email` -> `{ user: { email } }`). |
|
|
172
174
|
| `preserveUnmountedValues` | `boolean` | `false` | When true, caches values from fields that unmount from the DOM (e.g. multi-step wizard views). |
|
|
173
|
-
| `debounceMs` | `number` | `300` | Debounce delay for async field validators. |
|
|
175
|
+
| `debounceMs` | `number` | `300` | Debounce delay in milliseconds for async field validators. |
|
|
174
176
|
| `asyncValidators` | `Record<string, AsyncValidator>` | `undefined` | Key-value mapping of field names to asynchronous validation functions with `AbortSignal` cancellation. |
|
|
175
177
|
| `schema` | `SchemaLike` | `undefined` | Schema adapter (e.g. from `@native-form/zod`, `@native-form/yup`, `@native-form/valibot`). |
|
|
178
|
+
| `validator` | `Validator` | `undefined` | Custom validator object with a `validate(values, context)` method. |
|
|
176
179
|
| `onSubmit` | `(values, event?) => void` | `undefined` | Callback invoked when submission succeeds and passes validation. |
|
|
177
180
|
| `onInvalid` | `(errors, values, event?) => void` | `undefined` | Callback invoked when submission is blocked due to validation failures. |
|
|
178
181
|
|
package/dist/index.cjs
CHANGED
|
@@ -329,7 +329,8 @@ var FieldNode = class {
|
|
|
329
329
|
case "radio":
|
|
330
330
|
return this.element.checked ? this.element.value : void 0;
|
|
331
331
|
case "number":
|
|
332
|
-
|
|
332
|
+
case "range":
|
|
333
|
+
return this.element.value === "" || Number.isNaN(this.element.valueAsNumber) ? void 0 : this.element.valueAsNumber;
|
|
333
334
|
case "file":
|
|
334
335
|
return this.element.files;
|
|
335
336
|
default:
|
|
@@ -489,9 +490,9 @@ var FieldErrorRenderer = class _FieldErrorRenderer {
|
|
|
489
490
|
static nextErrorId = 0;
|
|
490
491
|
errorIds = /* @__PURE__ */ new WeakMap();
|
|
491
492
|
errorElements = /* @__PURE__ */ new WeakMap();
|
|
492
|
-
applyErrors(fields, errors) {
|
|
493
|
+
applyErrors(fields, errors, renderDomErrors = true) {
|
|
493
494
|
for (const field of fields) {
|
|
494
|
-
this.applyFieldError(field, errors[field.name]);
|
|
495
|
+
this.applyFieldError(field, errors[field.name], renderDomErrors);
|
|
495
496
|
}
|
|
496
497
|
}
|
|
497
498
|
normalizeFields(fields) {
|
|
@@ -499,12 +500,17 @@ var FieldErrorRenderer = class _FieldErrorRenderer {
|
|
|
499
500
|
this.normalizeField(field);
|
|
500
501
|
}
|
|
501
502
|
}
|
|
502
|
-
applyFieldError(field, message) {
|
|
503
|
+
applyFieldError(field, message, renderDomErrors = true) {
|
|
503
504
|
const element = field.element;
|
|
504
505
|
try {
|
|
505
506
|
element.setCustomValidity(message ?? "");
|
|
506
507
|
} catch (_e) {
|
|
507
508
|
}
|
|
509
|
+
if (!renderDomErrors) {
|
|
510
|
+
this.removeErrorElements(this.findExistingErrorElements(element));
|
|
511
|
+
this.errorElements.delete(element);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
508
514
|
if (!message) {
|
|
509
515
|
this.removeErrorElements(this.findExistingErrorElements(element));
|
|
510
516
|
this.errorElements.delete(element);
|
|
@@ -1283,7 +1289,11 @@ var Runtime = class {
|
|
|
1283
1289
|
const { node, values, value } = snapshot;
|
|
1284
1290
|
node.handleInput(value);
|
|
1285
1291
|
const mode = this.options.mode ?? "onChange";
|
|
1286
|
-
const
|
|
1292
|
+
const reValidateMode = this.options.reValidateMode ?? "onChange";
|
|
1293
|
+
const hasExistingError = Boolean(
|
|
1294
|
+
resolveFieldError(this.errors, element.name)
|
|
1295
|
+
);
|
|
1296
|
+
const shouldValidate = mode === "onChange" || mode === "all" || hasExistingError && reValidateMode === "onChange" || mode === "onTouched" && node.isTouched;
|
|
1287
1297
|
if (shouldValidate) {
|
|
1288
1298
|
this.triggerFieldValidation(node, values);
|
|
1289
1299
|
}
|
|
@@ -1308,7 +1318,12 @@ var Runtime = class {
|
|
|
1308
1318
|
const { node, values, value } = snapshot;
|
|
1309
1319
|
node.handleChange(value);
|
|
1310
1320
|
const mode = this.options.mode ?? "onChange";
|
|
1311
|
-
const
|
|
1321
|
+
const reValidateMode = this.options.reValidateMode ?? "onChange";
|
|
1322
|
+
const hasExistingError = Boolean(
|
|
1323
|
+
resolveFieldError(this.errors, element.name)
|
|
1324
|
+
);
|
|
1325
|
+
const isDiscreteControl = element instanceof HTMLSelectElement || element instanceof HTMLInputElement && (element.type === "checkbox" || element.type === "radio");
|
|
1326
|
+
const shouldValidate = mode === "onChange" || mode === "all" || isDiscreteControl || hasExistingError && reValidateMode === "onChange" || mode === "onTouched" && node.isTouched;
|
|
1312
1327
|
if (shouldValidate) {
|
|
1313
1328
|
this.triggerFieldValidation(node, values);
|
|
1314
1329
|
}
|
|
@@ -1470,8 +1485,12 @@ var Runtime = class {
|
|
|
1470
1485
|
const node = this.registry.get(element.name);
|
|
1471
1486
|
node?.handleBlur();
|
|
1472
1487
|
const mode = this.options.mode ?? "onChange";
|
|
1488
|
+
const reValidateMode = this.options.reValidateMode ?? "onChange";
|
|
1489
|
+
const hasExistingError = Boolean(
|
|
1490
|
+
resolveFieldError(this.errors, element.name)
|
|
1491
|
+
);
|
|
1473
1492
|
const hasPendingDebounce = this.debounceTimers.has(element.name);
|
|
1474
|
-
const shouldValidate = mode === "onBlur" || mode === "onTouched" || mode === "all" || hasPendingDebounce;
|
|
1493
|
+
const shouldValidate = mode === "onBlur" || mode === "onTouched" || mode === "all" || hasExistingError && reValidateMode === "onBlur" || hasPendingDebounce;
|
|
1475
1494
|
if (shouldValidate && node) {
|
|
1476
1495
|
this.triggerFieldValidation(node, values, true);
|
|
1477
1496
|
}
|
|
@@ -2017,7 +2036,12 @@ var Runtime = class {
|
|
|
2017
2036
|
if (observer) {
|
|
2018
2037
|
observer.disconnect();
|
|
2019
2038
|
}
|
|
2020
|
-
this.
|
|
2039
|
+
const renderDomErrors = this.options.renderDomErrors ?? true;
|
|
2040
|
+
this.fieldErrorRenderer.applyErrors(
|
|
2041
|
+
this.registry.values(),
|
|
2042
|
+
this.errors,
|
|
2043
|
+
renderDomErrors
|
|
2044
|
+
);
|
|
2021
2045
|
if (observer && this.form) {
|
|
2022
2046
|
observer.observe(this.form, runtimeMutationObserverOptions);
|
|
2023
2047
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -8,6 +8,14 @@
|
|
|
8
8
|
* @public
|
|
9
9
|
*/
|
|
10
10
|
type ValidationMode = "onChange" | "onBlur" | "onSubmit" | "onTouched" | "all";
|
|
11
|
+
/**
|
|
12
|
+
* Supported re-validation modes for fields that currently contain errors.
|
|
13
|
+
* - onChange: re-validates on every input/change event once an error is present
|
|
14
|
+
* - onBlur: re-validates only when the input loses focus
|
|
15
|
+
* - onSubmit: re-validates only on subsequent form submissions
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
type ReValidateMode = "onChange" | "onBlur" | "onSubmit";
|
|
11
19
|
/** @public */
|
|
12
20
|
type NativeFormElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
|
|
13
21
|
/**
|
|
@@ -284,6 +292,20 @@ declare function createValidationPipeline<TValues extends NativeFormValues = Nat
|
|
|
284
292
|
/** @public */
|
|
285
293
|
interface RuntimeOptions<TValues extends NativeFormValues = NativeFormValues> {
|
|
286
294
|
mode?: ValidationMode;
|
|
295
|
+
/**
|
|
296
|
+
* Validation strategy used after the form has encountered errors.
|
|
297
|
+
* - "onChange": field re-validates immediately on input/change when it has an error
|
|
298
|
+
* - "onBlur": field re-validates when blurred
|
|
299
|
+
* - "onSubmit": field re-validates only on subsequent submits
|
|
300
|
+
* @defaultValue "onChange"
|
|
301
|
+
*/
|
|
302
|
+
reValidateMode?: ReValidateMode;
|
|
303
|
+
/**
|
|
304
|
+
* Whether to inject native DOM error elements after inputs.
|
|
305
|
+
* Defaults to `true` in core, but `false` in `@native-form/react`.
|
|
306
|
+
* @defaultValue true
|
|
307
|
+
*/
|
|
308
|
+
renderDomErrors?: boolean;
|
|
287
309
|
serializationMode?: SerializationMode;
|
|
288
310
|
serializeFieldsOptions?: SerializeFieldsOptions;
|
|
289
311
|
nesting?: "flat" | "nested";
|
|
@@ -492,4 +514,4 @@ declare function setByPath(target: Record<string, unknown>, path: string | reado
|
|
|
492
514
|
declare function toNestedValues<TOutput extends Record<string, unknown> = Record<string, unknown>>(flatValues: Record<string, unknown>): TOutput;
|
|
493
515
|
|
|
494
516
|
export { FieldNode, Runtime, Serializer, arrayItemPath, createValidationPipeline, createValidator, formPath, getByPath, isArrayItemPath, matchArrayItemPath, normalizePath, parseArrayItemPath, remapArrayItemIndex, setByPath, shiftArrayItemIndex, splitPath, toNestedValues };
|
|
495
|
-
export type { ArrayItemPathInfo, AsyncFieldValidator, FieldEvents, FieldPathSegment, FieldState, FormEvents, InferSchemaOutput, NativeFormElement, NativeFormFlatValues, NativeFormNestedValue, NativeFormValue, NativeFormValues, RuntimeEvent, RuntimeEventType, RuntimeFormState, RuntimeListener, RuntimeOptions, RuntimePublicApi, SchemaLike, SerializationMode, SerializeFieldsOptions, StandardSchemaIssue, StandardSchemaResult, StandardSchemaV1, ValidationContext, ValidationEvents, ValidationFieldMeta, ValidationIssue, ValidationMode, ValidationPipeline, ValidationPipelineCapabilities, ValidationPipelineContext, ValidationPipelineOptions, ValidationResult, Validator };
|
|
517
|
+
export type { ArrayItemPathInfo, AsyncFieldValidator, FieldEvents, FieldPathSegment, FieldState, FormEvents, InferSchemaOutput, NativeFormElement, NativeFormFlatValues, NativeFormNestedValue, NativeFormValue, NativeFormValues, ReValidateMode, RuntimeEvent, RuntimeEventType, RuntimeFormState, RuntimeListener, RuntimeOptions, RuntimePublicApi, SchemaLike, SerializationMode, SerializeFieldsOptions, StandardSchemaIssue, StandardSchemaResult, StandardSchemaV1, ValidationContext, ValidationEvents, ValidationFieldMeta, ValidationIssue, ValidationMode, ValidationPipeline, ValidationPipelineCapabilities, ValidationPipelineContext, ValidationPipelineOptions, ValidationResult, Validator };
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,14 @@
|
|
|
8
8
|
* @public
|
|
9
9
|
*/
|
|
10
10
|
type ValidationMode = "onChange" | "onBlur" | "onSubmit" | "onTouched" | "all";
|
|
11
|
+
/**
|
|
12
|
+
* Supported re-validation modes for fields that currently contain errors.
|
|
13
|
+
* - onChange: re-validates on every input/change event once an error is present
|
|
14
|
+
* - onBlur: re-validates only when the input loses focus
|
|
15
|
+
* - onSubmit: re-validates only on subsequent form submissions
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
type ReValidateMode = "onChange" | "onBlur" | "onSubmit";
|
|
11
19
|
/** @public */
|
|
12
20
|
type NativeFormElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
|
|
13
21
|
/**
|
|
@@ -284,6 +292,20 @@ declare function createValidationPipeline<TValues extends NativeFormValues = Nat
|
|
|
284
292
|
/** @public */
|
|
285
293
|
interface RuntimeOptions<TValues extends NativeFormValues = NativeFormValues> {
|
|
286
294
|
mode?: ValidationMode;
|
|
295
|
+
/**
|
|
296
|
+
* Validation strategy used after the form has encountered errors.
|
|
297
|
+
* - "onChange": field re-validates immediately on input/change when it has an error
|
|
298
|
+
* - "onBlur": field re-validates when blurred
|
|
299
|
+
* - "onSubmit": field re-validates only on subsequent submits
|
|
300
|
+
* @defaultValue "onChange"
|
|
301
|
+
*/
|
|
302
|
+
reValidateMode?: ReValidateMode;
|
|
303
|
+
/**
|
|
304
|
+
* Whether to inject native DOM error elements after inputs.
|
|
305
|
+
* Defaults to `true` in core, but `false` in `@native-form/react`.
|
|
306
|
+
* @defaultValue true
|
|
307
|
+
*/
|
|
308
|
+
renderDomErrors?: boolean;
|
|
287
309
|
serializationMode?: SerializationMode;
|
|
288
310
|
serializeFieldsOptions?: SerializeFieldsOptions;
|
|
289
311
|
nesting?: "flat" | "nested";
|
|
@@ -492,4 +514,4 @@ declare function setByPath(target: Record<string, unknown>, path: string | reado
|
|
|
492
514
|
declare function toNestedValues<TOutput extends Record<string, unknown> = Record<string, unknown>>(flatValues: Record<string, unknown>): TOutput;
|
|
493
515
|
|
|
494
516
|
export { FieldNode, Runtime, Serializer, arrayItemPath, createValidationPipeline, createValidator, formPath, getByPath, isArrayItemPath, matchArrayItemPath, normalizePath, parseArrayItemPath, remapArrayItemIndex, setByPath, shiftArrayItemIndex, splitPath, toNestedValues };
|
|
495
|
-
export type { ArrayItemPathInfo, AsyncFieldValidator, FieldEvents, FieldPathSegment, FieldState, FormEvents, InferSchemaOutput, NativeFormElement, NativeFormFlatValues, NativeFormNestedValue, NativeFormValue, NativeFormValues, RuntimeEvent, RuntimeEventType, RuntimeFormState, RuntimeListener, RuntimeOptions, RuntimePublicApi, SchemaLike, SerializationMode, SerializeFieldsOptions, StandardSchemaIssue, StandardSchemaResult, StandardSchemaV1, ValidationContext, ValidationEvents, ValidationFieldMeta, ValidationIssue, ValidationMode, ValidationPipeline, ValidationPipelineCapabilities, ValidationPipelineContext, ValidationPipelineOptions, ValidationResult, Validator };
|
|
517
|
+
export type { ArrayItemPathInfo, AsyncFieldValidator, FieldEvents, FieldPathSegment, FieldState, FormEvents, InferSchemaOutput, NativeFormElement, NativeFormFlatValues, NativeFormNestedValue, NativeFormValue, NativeFormValues, ReValidateMode, RuntimeEvent, RuntimeEventType, RuntimeFormState, RuntimeListener, RuntimeOptions, RuntimePublicApi, SchemaLike, SerializationMode, SerializeFieldsOptions, StandardSchemaIssue, StandardSchemaResult, StandardSchemaV1, ValidationContext, ValidationEvents, ValidationFieldMeta, ValidationIssue, ValidationMode, ValidationPipeline, ValidationPipelineCapabilities, ValidationPipelineContext, ValidationPipelineOptions, ValidationResult, Validator };
|
package/dist/index.js
CHANGED
|
@@ -287,7 +287,8 @@ var FieldNode = class {
|
|
|
287
287
|
case "radio":
|
|
288
288
|
return this.element.checked ? this.element.value : void 0;
|
|
289
289
|
case "number":
|
|
290
|
-
|
|
290
|
+
case "range":
|
|
291
|
+
return this.element.value === "" || Number.isNaN(this.element.valueAsNumber) ? void 0 : this.element.valueAsNumber;
|
|
291
292
|
case "file":
|
|
292
293
|
return this.element.files;
|
|
293
294
|
default:
|
|
@@ -447,9 +448,9 @@ var FieldErrorRenderer = class _FieldErrorRenderer {
|
|
|
447
448
|
static nextErrorId = 0;
|
|
448
449
|
errorIds = /* @__PURE__ */ new WeakMap();
|
|
449
450
|
errorElements = /* @__PURE__ */ new WeakMap();
|
|
450
|
-
applyErrors(fields, errors) {
|
|
451
|
+
applyErrors(fields, errors, renderDomErrors = true) {
|
|
451
452
|
for (const field of fields) {
|
|
452
|
-
this.applyFieldError(field, errors[field.name]);
|
|
453
|
+
this.applyFieldError(field, errors[field.name], renderDomErrors);
|
|
453
454
|
}
|
|
454
455
|
}
|
|
455
456
|
normalizeFields(fields) {
|
|
@@ -457,12 +458,17 @@ var FieldErrorRenderer = class _FieldErrorRenderer {
|
|
|
457
458
|
this.normalizeField(field);
|
|
458
459
|
}
|
|
459
460
|
}
|
|
460
|
-
applyFieldError(field, message) {
|
|
461
|
+
applyFieldError(field, message, renderDomErrors = true) {
|
|
461
462
|
const element = field.element;
|
|
462
463
|
try {
|
|
463
464
|
element.setCustomValidity(message ?? "");
|
|
464
465
|
} catch (_e) {
|
|
465
466
|
}
|
|
467
|
+
if (!renderDomErrors) {
|
|
468
|
+
this.removeErrorElements(this.findExistingErrorElements(element));
|
|
469
|
+
this.errorElements.delete(element);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
466
472
|
if (!message) {
|
|
467
473
|
this.removeErrorElements(this.findExistingErrorElements(element));
|
|
468
474
|
this.errorElements.delete(element);
|
|
@@ -1241,7 +1247,11 @@ var Runtime = class {
|
|
|
1241
1247
|
const { node, values, value } = snapshot;
|
|
1242
1248
|
node.handleInput(value);
|
|
1243
1249
|
const mode = this.options.mode ?? "onChange";
|
|
1244
|
-
const
|
|
1250
|
+
const reValidateMode = this.options.reValidateMode ?? "onChange";
|
|
1251
|
+
const hasExistingError = Boolean(
|
|
1252
|
+
resolveFieldError(this.errors, element.name)
|
|
1253
|
+
);
|
|
1254
|
+
const shouldValidate = mode === "onChange" || mode === "all" || hasExistingError && reValidateMode === "onChange" || mode === "onTouched" && node.isTouched;
|
|
1245
1255
|
if (shouldValidate) {
|
|
1246
1256
|
this.triggerFieldValidation(node, values);
|
|
1247
1257
|
}
|
|
@@ -1266,7 +1276,12 @@ var Runtime = class {
|
|
|
1266
1276
|
const { node, values, value } = snapshot;
|
|
1267
1277
|
node.handleChange(value);
|
|
1268
1278
|
const mode = this.options.mode ?? "onChange";
|
|
1269
|
-
const
|
|
1279
|
+
const reValidateMode = this.options.reValidateMode ?? "onChange";
|
|
1280
|
+
const hasExistingError = Boolean(
|
|
1281
|
+
resolveFieldError(this.errors, element.name)
|
|
1282
|
+
);
|
|
1283
|
+
const isDiscreteControl = element instanceof HTMLSelectElement || element instanceof HTMLInputElement && (element.type === "checkbox" || element.type === "radio");
|
|
1284
|
+
const shouldValidate = mode === "onChange" || mode === "all" || isDiscreteControl || hasExistingError && reValidateMode === "onChange" || mode === "onTouched" && node.isTouched;
|
|
1270
1285
|
if (shouldValidate) {
|
|
1271
1286
|
this.triggerFieldValidation(node, values);
|
|
1272
1287
|
}
|
|
@@ -1428,8 +1443,12 @@ var Runtime = class {
|
|
|
1428
1443
|
const node = this.registry.get(element.name);
|
|
1429
1444
|
node?.handleBlur();
|
|
1430
1445
|
const mode = this.options.mode ?? "onChange";
|
|
1446
|
+
const reValidateMode = this.options.reValidateMode ?? "onChange";
|
|
1447
|
+
const hasExistingError = Boolean(
|
|
1448
|
+
resolveFieldError(this.errors, element.name)
|
|
1449
|
+
);
|
|
1431
1450
|
const hasPendingDebounce = this.debounceTimers.has(element.name);
|
|
1432
|
-
const shouldValidate = mode === "onBlur" || mode === "onTouched" || mode === "all" || hasPendingDebounce;
|
|
1451
|
+
const shouldValidate = mode === "onBlur" || mode === "onTouched" || mode === "all" || hasExistingError && reValidateMode === "onBlur" || hasPendingDebounce;
|
|
1433
1452
|
if (shouldValidate && node) {
|
|
1434
1453
|
this.triggerFieldValidation(node, values, true);
|
|
1435
1454
|
}
|
|
@@ -1975,7 +1994,12 @@ var Runtime = class {
|
|
|
1975
1994
|
if (observer) {
|
|
1976
1995
|
observer.disconnect();
|
|
1977
1996
|
}
|
|
1978
|
-
this.
|
|
1997
|
+
const renderDomErrors = this.options.renderDomErrors ?? true;
|
|
1998
|
+
this.fieldErrorRenderer.applyErrors(
|
|
1999
|
+
this.registry.values(),
|
|
2000
|
+
this.errors,
|
|
2001
|
+
renderDomErrors
|
|
2002
|
+
);
|
|
1979
2003
|
if (observer && this.form) {
|
|
1980
2004
|
observer.observe(this.form, runtimeMutationObserverOptions);
|
|
1981
2005
|
}
|