@object-ui/fields 17.3.0 → 17.6.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/CHANGELOG.md +1655 -0
- package/README.md +1 -12
- package/dist/FieldEditWidget.d.ts +23 -3
- package/dist/currency.d.ts +46 -0
- package/dist/field-type-alias.d.ts +35 -1
- package/dist/index.cjs +4 -4
- package/dist/index.css +651 -0
- package/dist/index.d.ts +474 -85
- package/dist/index.js +5418 -5023
- package/dist/widgets/AddressField.d.ts +4 -12
- package/dist/widgets/AutoNumberField.d.ts +1 -1
- package/dist/widgets/AvatarField.d.ts +1 -1
- package/dist/widgets/BooleanField.d.ts +1 -1
- package/dist/widgets/CapabilityMultiSelectField.d.ts +1 -1
- package/dist/widgets/CheckboxesField.d.ts +1 -1
- package/dist/widgets/CodeField.d.ts +1 -1
- package/dist/widgets/ColorField.d.ts +1 -1
- package/dist/widgets/CurrencyField.d.ts +1 -1
- package/dist/widgets/DateField.d.ts +1 -1
- package/dist/widgets/DateTimeField.d.ts +1 -1
- package/dist/widgets/EmailField.d.ts +1 -1
- package/dist/widgets/FileField.d.ts +18 -2
- package/dist/widgets/FilterConditionField.d.ts +28 -1
- package/dist/widgets/FormulaField.d.ts +1 -1
- package/dist/widgets/FullscreenFieldEditor.d.ts +49 -152
- package/dist/widgets/GeolocationField.d.ts +1 -1
- package/dist/widgets/GridField.d.ts +68 -6
- package/dist/widgets/ImageField.d.ts +1 -1
- package/dist/widgets/LocationField.d.ts +1 -1
- package/dist/widgets/LookupField.d.ts +1 -1
- package/dist/widgets/MasterDetailField.d.ts +1 -1
- package/dist/widgets/MultiSelectField.d.ts +1 -1
- package/dist/widgets/NumberField.d.ts +1 -1
- package/dist/widgets/ObjectField.d.ts +1 -1
- package/dist/widgets/ObjectRefField.d.ts +1 -1
- package/dist/widgets/OptionsEmptyState.d.ts +21 -1
- package/dist/widgets/PasswordField.d.ts +1 -1
- package/dist/widgets/PercentField.d.ts +1 -1
- package/dist/widgets/PhoneField.d.ts +1 -1
- package/dist/widgets/QRCodeField.d.ts +1 -1
- package/dist/widgets/RadioField.d.ts +1 -1
- package/dist/widgets/RatingField.d.ts +1 -1
- package/dist/widgets/RecipientPickerField.d.ts +1 -1
- package/dist/widgets/RecordPickerDialog.d.ts +34 -10
- package/dist/widgets/RichTextField.d.ts +37 -1
- package/dist/widgets/SelectField.d.ts +26 -6
- package/dist/widgets/SignatureField.d.ts +32 -2
- package/dist/widgets/SliderField.d.ts +37 -2
- package/dist/widgets/SummaryField.d.ts +1 -1
- package/dist/widgets/TagsField.d.ts +1 -1
- package/dist/widgets/TextAreaField.d.ts +6 -2
- package/dist/widgets/TextField.d.ts +1 -1
- package/dist/widgets/TimeField.d.ts +1 -1
- package/dist/widgets/UrlField.d.ts +1 -1
- package/dist/widgets/UserField.d.ts +1 -1
- package/dist/widgets/VectorField.d.ts +1 -1
- package/dist/widgets/_cell-renderer-bridge.d.ts +1 -1
- package/dist/widgets/address-format.d.ts +88 -0
- package/dist/widgets/file-size-guard.d.ts +41 -0
- package/dist/widgets/lookupColumnDisplay.d.ts +93 -0
- package/dist/widgets/nativeDateValue.d.ts +37 -0
- package/dist/widgets/richTextDisplay.d.ts +80 -0
- package/dist/widgets/toDomProps.d.ts +30 -10
- package/dist/widgets/toHostGroupProps.d.ts +189 -0
- package/dist/widgets/types.d.ts +8 -3
- package/dist/widgets/useRecordQuery.d.ts +7 -1
- package/package.json +17 -14
- package/dist/FieldEditWidget.test.d.ts +0 -8
- package/dist/__tests__/DateCellRenderer.test.d.ts +0 -0
- package/dist/__tests__/NumberCellRenderer.test.d.ts +0 -0
- package/dist/__tests__/cell-truncation.test.d.ts +0 -0
- package/dist/__tests__/field-carrier-sdui.test.d.ts +0 -0
- package/dist/__tests__/lookupCellDisplayField.test.d.ts +0 -0
- package/dist/__tests__/lookupCellDisplayName.test.d.ts +0 -0
- package/dist/__tests__/lookupCellReferenceKey.test.d.ts +0 -0
- package/dist/__tests__/registry-collision.test.d.ts +0 -0
- package/dist/__tests__/resolveFieldCurrency.test.d.ts +0 -1
- package/dist/__tests__/spec-symbol-batch7.test.d.ts +0 -8
- package/dist/__tests__/validation-feedback.test.d.ts +0 -0
- package/dist/__tests__/widget-aria-invalid-e2e.test.d.ts +0 -0
- package/dist/__tests__/widget-aria-invalid-registry-e2e.test.d.ts +0 -0
- package/dist/__tests__/widget-dom-leak-e2e.test.d.ts +0 -0
- package/dist/__tests__/widget-props-contract.test.d.ts +0 -8
- package/dist/coerce-safe-value.test.d.ts +0 -1
- package/dist/complex-widgets.test.d.ts +0 -1
- package/dist/datetime-widgets.test.d.ts +0 -1
- package/dist/field-type-coverage.test.d.ts +0 -1
- package/dist/standard-widgets.test.d.ts +0 -0
- package/dist/widgets/AddressField.uniqueIds.test.d.ts +0 -0
- package/dist/widgets/CapabilityMultiSelectField.test.d.ts +0 -0
- package/dist/widgets/CharacterCount.d.ts +0 -56
- package/dist/widgets/CheckboxesField.cascade.test.d.ts +0 -0
- package/dist/widgets/GeolocationField.uniqueIds.test.d.ts +0 -0
- package/dist/widgets/GridField.test.d.ts +0 -1
- package/dist/widgets/ImageLightbox.test.d.ts +0 -1
- package/dist/widgets/LookupField.dependsOn.test.d.ts +0 -8
- package/dist/widgets/LookupField.gateHintLabel.test.d.ts +0 -8
- package/dist/widgets/LookupField.hydration.test.d.ts +0 -1
- package/dist/widgets/LookupField.idField.test.d.ts +0 -1
- package/dist/widgets/MultiSelectField.cascade.test.d.ts +0 -0
- package/dist/widgets/OptionsEmptyState.no-provider.test.d.ts +0 -0
- package/dist/widgets/OptionsEmptyState.test.d.ts +0 -0
- package/dist/widgets/PeoplePicker.test.d.ts +0 -8
- package/dist/widgets/PeoplePickerComponents.test.d.ts +0 -8
- package/dist/widgets/RadioField.cascade.test.d.ts +0 -0
- package/dist/widgets/RecipientPickerField.test.d.ts +0 -0
- package/dist/widgets/RecordPickerDialog.filterOptions.test.d.ts +0 -8
- package/dist/widgets/RecordPickerDialog.selectLabel.test.d.ts +0 -8
- package/dist/widgets/SelectField.cascade.test.d.ts +0 -0
- package/dist/widgets/TagsField.placeholder.no-provider.test.d.ts +0 -0
- package/dist/widgets/TagsField.placeholder.test.d.ts +0 -0
- package/dist/widgets/UserFieldPicker.test.d.ts +0 -8
- package/dist/widgets/__tests__/FilterConditionField.matchAll.test.d.ts +0 -8
- package/dist/widgets/__tests__/FilterConditionField.operators.test.d.ts +0 -8
- package/dist/widgets/__tests__/FullscreenFieldEditor.i18n.test.d.ts +0 -0
- package/dist/widgets/__tests__/FullscreenFieldEditor.no-provider.test.d.ts +0 -0
- package/dist/widgets/__tests__/RichTextField.fullscreenDisabled.test.d.ts +0 -0
- package/dist/widgets/__tests__/RichTextField.mobileFullscreen.test.d.ts +0 -0
- package/dist/widgets/__tests__/TextAreaField.characterCount.announcements.test.d.ts +0 -0
- package/dist/widgets/__tests__/TextAreaField.characterCount.i18n.test.d.ts +0 -0
- package/dist/widgets/__tests__/TextAreaField.characterCount.no-provider.test.d.ts +0 -0
- package/dist/widgets/__tests__/TextAreaField.fullscreenCharacterCount.i18n.test.d.ts +0 -0
- package/dist/widgets/__tests__/TextAreaField.fullscreenCharacterCount.test.d.ts +0 -0
- package/dist/widgets/__tests__/TextAreaField.fullscreenDisabled.test.d.ts +0 -0
- package/dist/widgets/__tests__/TextAreaField.mobileFullscreen.test.d.ts +0 -0
- package/dist/widgets/deriveLookupColumns.test.d.ts +0 -1
- package/dist/widgets/file-value.test.d.ts +0 -1
- package/dist/widgets/personDisplay.test.d.ts +0 -8
- package/dist/widgets/recentLookups.test.d.ts +0 -1
- package/dist/widgets/useRecordQuery.test.d.ts +0 -8
- package/dist/widgets/useUploadingSignal.test.d.ts +0 -1
- package/src/FieldEditWidget.test.ts +0 -133
- package/src/FieldEditWidget.tsx +0 -191
- package/src/__tests__/DateCellRenderer.test.tsx +0 -73
- package/src/__tests__/NumberCellRenderer.test.tsx +0 -60
- package/src/__tests__/cell-truncation.test.tsx +0 -96
- package/src/__tests__/field-carrier-sdui.test.tsx +0 -169
- package/src/__tests__/lookupCellDisplayField.test.tsx +0 -107
- package/src/__tests__/lookupCellDisplayName.test.tsx +0 -119
- package/src/__tests__/lookupCellReferenceKey.test.tsx +0 -57
- package/src/__tests__/registry-collision.test.ts +0 -91
- package/src/__tests__/resolveFieldCurrency.test.ts +0 -20
- package/src/__tests__/spec-symbol-batch7.test.ts +0 -126
- package/src/__tests__/validation-feedback.test.tsx +0 -361
- package/src/__tests__/widget-aria-invalid-e2e.test.tsx +0 -265
- package/src/__tests__/widget-aria-invalid-registry-e2e.test.tsx +0 -338
- package/src/__tests__/widget-dom-leak-e2e.test.tsx +0 -598
- package/src/__tests__/widget-props-contract.test.tsx +0 -142
- package/src/coerce-safe-value.test.ts +0 -34
- package/src/complex-widgets.test.tsx +0 -908
- package/src/currency.ts +0 -14
- package/src/datetime-widgets.test.tsx +0 -93
- package/src/field-type-alias.ts +0 -100
- package/src/field-type-coverage.test.ts +0 -67
- package/src/index.tsx +0 -2503
- package/src/standard-widgets.test.tsx +0 -357
- package/src/widgets/AddressField.tsx +0 -132
- package/src/widgets/AddressField.uniqueIds.test.tsx +0 -92
- package/src/widgets/AutoNumberField.tsx +0 -18
- package/src/widgets/AvatarField.tsx +0 -117
- package/src/widgets/BooleanField.tsx +0 -52
- package/src/widgets/CapabilityMultiSelectField.test.tsx +0 -168
- package/src/widgets/CapabilityMultiSelectField.tsx +0 -229
- package/src/widgets/CharacterCount.tsx +0 -263
- package/src/widgets/CheckboxesField.cascade.test.tsx +0 -143
- package/src/widgets/CheckboxesField.tsx +0 -136
- package/src/widgets/CodeField.tsx +0 -41
- package/src/widgets/ColorField.tsx +0 -53
- package/src/widgets/CurrencyField.tsx +0 -95
- package/src/widgets/DateField.tsx +0 -31
- package/src/widgets/DateTimeField.tsx +0 -37
- package/src/widgets/EmailField.tsx +0 -37
- package/src/widgets/FileField.tsx +0 -494
- package/src/widgets/FilterConditionField.tsx +0 -454
- package/src/widgets/FormulaField.tsx +0 -33
- package/src/widgets/FullscreenFieldEditor.tsx +0 -271
- package/src/widgets/GeolocationField.tsx +0 -167
- package/src/widgets/GeolocationField.uniqueIds.test.tsx +0 -88
- package/src/widgets/GridField.test.tsx +0 -406
- package/src/widgets/GridField.tsx +0 -966
- package/src/widgets/ImageCropperDialog.tsx +0 -245
- package/src/widgets/ImageField.tsx +0 -255
- package/src/widgets/ImageLightbox.test.tsx +0 -52
- package/src/widgets/ImageLightbox.tsx +0 -108
- package/src/widgets/LocationField.tsx +0 -57
- package/src/widgets/LookupField.dependsOn.test.tsx +0 -233
- package/src/widgets/LookupField.gateHintLabel.test.tsx +0 -135
- package/src/widgets/LookupField.hydration.test.tsx +0 -136
- package/src/widgets/LookupField.idField.test.tsx +0 -61
- package/src/widgets/LookupField.tsx +0 -1291
- package/src/widgets/MarkdownContent.tsx +0 -19
- package/src/widgets/MasterDetailField.tsx +0 -131
- package/src/widgets/MultiSelectField.cascade.test.tsx +0 -143
- package/src/widgets/MultiSelectField.tsx +0 -142
- package/src/widgets/NumberField.tsx +0 -49
- package/src/widgets/ObjectField.tsx +0 -94
- package/src/widgets/ObjectRefField.tsx +0 -121
- package/src/widgets/OptionsEmptyState.no-provider.test.tsx +0 -88
- package/src/widgets/OptionsEmptyState.test.tsx +0 -169
- package/src/widgets/OptionsEmptyState.tsx +0 -83
- package/src/widgets/PasswordField.tsx +0 -49
- package/src/widgets/PeoplePicker.test.tsx +0 -360
- package/src/widgets/PeoplePicker.tsx +0 -579
- package/src/widgets/PeoplePickerComponents.test.tsx +0 -104
- package/src/widgets/PercentField.tsx +0 -95
- package/src/widgets/PersonRow.tsx +0 -117
- package/src/widgets/PhoneField.tsx +0 -37
- package/src/widgets/QRCodeField.tsx +0 -103
- package/src/widgets/RadioField.cascade.test.tsx +0 -141
- package/src/widgets/RadioField.tsx +0 -111
- package/src/widgets/RatingField.tsx +0 -81
- package/src/widgets/RecipientPickerField.test.tsx +0 -157
- package/src/widgets/RecipientPickerField.tsx +0 -188
- package/src/widgets/RecordPickerDialog.filterOptions.test.tsx +0 -611
- package/src/widgets/RecordPickerDialog.selectLabel.test.tsx +0 -112
- package/src/widgets/RecordPickerDialog.tsx +0 -1316
- package/src/widgets/RichTextField.tsx +0 -176
- package/src/widgets/SelectField.cascade.test.tsx +0 -138
- package/src/widgets/SelectField.tsx +0 -162
- package/src/widgets/SelectionTray.tsx +0 -110
- package/src/widgets/SignatureField.tsx +0 -140
- package/src/widgets/SliderField.tsx +0 -39
- package/src/widgets/SummaryField.tsx +0 -31
- package/src/widgets/TagsField.placeholder.no-provider.test.tsx +0 -68
- package/src/widgets/TagsField.placeholder.test.tsx +0 -95
- package/src/widgets/TagsField.tsx +0 -88
- package/src/widgets/TextAreaField.tsx +0 -213
- package/src/widgets/TextField.tsx +0 -45
- package/src/widgets/TimeField.tsx +0 -31
- package/src/widgets/UrlField.tsx +0 -46
- package/src/widgets/UserField.tsx +0 -58
- package/src/widgets/UserFieldPicker.test.tsx +0 -78
- package/src/widgets/VectorField.tsx +0 -29
- package/src/widgets/__tests__/FilterConditionField.matchAll.test.ts +0 -66
- package/src/widgets/__tests__/FilterConditionField.operators.test.ts +0 -142
- package/src/widgets/__tests__/FullscreenFieldEditor.i18n.test.tsx +0 -248
- package/src/widgets/__tests__/FullscreenFieldEditor.no-provider.test.tsx +0 -116
- package/src/widgets/__tests__/RichTextField.fullscreenDisabled.test.tsx +0 -244
- package/src/widgets/__tests__/RichTextField.mobileFullscreen.test.tsx +0 -195
- package/src/widgets/__tests__/TextAreaField.characterCount.announcements.test.tsx +0 -417
- package/src/widgets/__tests__/TextAreaField.characterCount.i18n.test.tsx +0 -287
- package/src/widgets/__tests__/TextAreaField.characterCount.no-provider.test.tsx +0 -160
- package/src/widgets/__tests__/TextAreaField.fullscreenCharacterCount.i18n.test.tsx +0 -161
- package/src/widgets/__tests__/TextAreaField.fullscreenCharacterCount.test.tsx +0 -346
- package/src/widgets/__tests__/TextAreaField.fullscreenDisabled.test.tsx +0 -246
- package/src/widgets/__tests__/TextAreaField.mobileFullscreen.test.tsx +0 -175
- package/src/widgets/_cell-renderer-bridge.ts +0 -19
- package/src/widgets/deriveLookupColumns.test.ts +0 -112
- package/src/widgets/deriveLookupColumns.ts +0 -108
- package/src/widgets/file-value.test.ts +0 -230
- package/src/widgets/file-value.ts +0 -246
- package/src/widgets/openNativePicker.ts +0 -18
- package/src/widgets/personDisplay.test.ts +0 -113
- package/src/widgets/personDisplay.ts +0 -92
- package/src/widgets/recentLookups.test.ts +0 -45
- package/src/widgets/recentLookups.ts +0 -47
- package/src/widgets/toDomProps.ts +0 -175
- package/src/widgets/types.ts +0 -242
- package/src/widgets/useCascadingOptions.ts +0 -46
- package/src/widgets/useFieldTranslation.ts +0 -150
- package/src/widgets/useRecordQuery.test.ts +0 -174
- package/src/widgets/useRecordQuery.ts +0 -264
- package/src/widgets/useUploadingSignal.test.tsx +0 -62
- package/src/widgets/useUploadingSignal.ts +0 -20
- package/src/withFieldCarrier.tsx +0 -75
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ObjectUI
|
|
3
|
-
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
-
*
|
|
5
|
-
* This source code is licensed under the MIT license found in the
|
|
6
|
-
* LICENSE file in the root directory of this source tree.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* `@object-ui/fields` ↔ `@objectstack/spec` symbol-collision guards
|
|
11
|
-
* (objectui#3161, objectstack#4115 ledger batch 7).
|
|
12
|
-
*
|
|
13
|
-
* Two symbols, two different verdicts:
|
|
14
|
-
*
|
|
15
|
-
* - `isFileIdToken` was a character-for-character copy of the spec's function
|
|
16
|
-
* under the spec's own name. It is a re-export now, and the gate below is
|
|
17
|
-
* REFERENCE IDENTITY — not behaviour. A faithful copy passes every behaviour
|
|
18
|
-
* test ever written for it (objectui#3003, re-proved on
|
|
19
|
-
* `isAggregatedViewContainer` in objectui#3169), so `toBe` is the only
|
|
20
|
-
* assertion that can tell a re-export from a fork.
|
|
21
|
-
*
|
|
22
|
-
* - `FieldWidgetProps` is now `FieldWidgetComponentProps`. The spec owns that
|
|
23
|
-
* name for the DECLARED widget-plugin props contract; this package's is the
|
|
24
|
-
* React interface its widgets implement. The pins record what still makes
|
|
25
|
-
* re-exporting impossible (`field` is the far richer `FieldMetadata` here)
|
|
26
|
-
* and, since objectui#3222, what no longer does: the validation slot is the
|
|
27
|
-
* spec's `error` on both sides.
|
|
28
|
-
*
|
|
29
|
-
* The other direction — "the spec must not start owning the NEW names" — is not
|
|
30
|
-
* asserted here because `scripts/check-spec-symbol-derivation.mjs` already
|
|
31
|
-
* answers it on every CI run, for every name in the package rather than the
|
|
32
|
-
* handful a test would remember to list.
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
import { describe, it, expect } from 'vitest';
|
|
36
|
-
import { isFileIdToken as specIsFileIdToken } from '@objectstack/spec/data';
|
|
37
|
-
import type { FieldWidgetProps as SpecFieldWidgetProps } from '@objectstack/spec/ui';
|
|
38
|
-
|
|
39
|
-
import { isFileIdToken, fileIdOf } from '../widgets/file-value';
|
|
40
|
-
import type { FieldWidgetComponentProps } from '../widgets/types';
|
|
41
|
-
|
|
42
|
-
describe('isFileIdToken IS the spec function', () => {
|
|
43
|
-
it('is the same function object, not an equivalent one', () => {
|
|
44
|
-
// The whole point. Every value/behaviour assertion below this line would
|
|
45
|
-
// also pass against a re-forked copy; this one would not.
|
|
46
|
-
expect(isFileIdToken).toBe(specIsFileIdToken);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('is the arbiter the rest of the module routes through', () => {
|
|
50
|
-
// `fileIdOf` must keep agreeing with the shared predicate, so a future
|
|
51
|
-
// "small local tweak" to the id shape cannot be reintroduced one caller at
|
|
52
|
-
// a time.
|
|
53
|
-
for (const value of ['abc123', 'AbC-_9', 'x'.repeat(64)]) {
|
|
54
|
-
expect(specIsFileIdToken(value)).toBe(true);
|
|
55
|
-
expect(fileIdOf(value)).toBe(value);
|
|
56
|
-
}
|
|
57
|
-
for (const value of ['https://example.com/f.png', '/api/v1/storage/files/1', 'a.b', 'x'.repeat(65)]) {
|
|
58
|
-
expect(specIsFileIdToken(value)).toBe(false);
|
|
59
|
-
expect(fileIdOf(value)).toBeUndefined();
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
/* -------------------------------------------------------------------------- */
|
|
65
|
-
/* Compile-time pins. A violation is a `tsc` error, not a runtime failure. */
|
|
66
|
-
/* This package's tsconfig.json includes its tests, so `type-check` compiles */
|
|
67
|
-
/* them (scripts/check-type-check-coverage.mjs keeps that true). */
|
|
68
|
-
/* -------------------------------------------------------------------------- */
|
|
69
|
-
|
|
70
|
-
type Assert<T extends true> = T;
|
|
71
|
-
type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
|
|
72
|
-
type Extends<A, B> = [A] extends [B] ? true : false;
|
|
73
|
-
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
74
|
-
type HasKey<T, K extends string> = K extends keyof T ? true : false;
|
|
75
|
-
|
|
76
|
-
describe('FieldWidgetComponentProps is the RENDERED layer of the spec contract', () => {
|
|
77
|
-
it('is pinned at compile time', () => {
|
|
78
|
-
// The import above is itself half the tripwire: the day the spec stops
|
|
79
|
-
// exporting `FieldWidgetProps`, this file stops compiling and the rename's
|
|
80
|
-
// reason is up for re-triage (the plain name could come back).
|
|
81
|
-
type _SpecIsReal = Assert<Equal<IsAny<SpecFieldWidgetProps>, false>>;
|
|
82
|
-
|
|
83
|
-
// The validation-message slot no longer diverges. objectui#3161 could only
|
|
84
|
-
// RECORD that the declared contract called it `error` while every widget
|
|
85
|
-
// here read `errorMessage`; objectui#3222 resolved it in the direction the
|
|
86
|
-
// contract points — this package adopted `error`, and the form renderer
|
|
87
|
-
// started producing it, so the name and the delivery landed together.
|
|
88
|
-
// Both sides are pinned: the spec still owns the name, and this package
|
|
89
|
-
// still spells it the same way.
|
|
90
|
-
type _SpecNamesItError = Assert<HasKey<SpecFieldWidgetProps, 'error'>>;
|
|
91
|
-
type _LocalNamesItErrorToo = Assert<HasKey<FieldWidgetComponentProps, 'error'>>;
|
|
92
|
-
type _SameOptionalStringSlot = Assert<
|
|
93
|
-
Equal<FieldWidgetComponentProps['error'], SpecFieldWidgetProps['error']>
|
|
94
|
-
>;
|
|
95
|
-
// The old name is gone, not kept as an alias. An alias would be exactly the
|
|
96
|
-
// lenient second dialect AGENTS.md #0.1 forbids — and the reason a missed
|
|
97
|
-
// call site would have gone quiet again.
|
|
98
|
-
type _OldNameRetired = Assert<Equal<HasKey<FieldWidgetComponentProps, 'errorMessage'>, false>>;
|
|
99
|
-
|
|
100
|
-
// …and the reason nobody noticed used to sit right here: three pins
|
|
101
|
-
// asserting that `[key: string]: any` was still present, and that
|
|
102
|
-
// `props.required` / `props.error` therefore read as `any`. objectui#3221
|
|
103
|
-
// removed that index signature, so those pins have gone red on purpose and
|
|
104
|
-
// are gone with it. What replaces them is the inverse claim — the type is
|
|
105
|
-
// now CLOSED, so a key the spec declares and this one does not can finally
|
|
106
|
-
// be reported as missing. That is what made objectui#3222's rename
|
|
107
|
-
// decidable by the compiler instead of by a symbol guard.
|
|
108
|
-
type _NoStringIndexSignature = Assert<Equal<Extends<string, keyof FieldWidgetComponentProps>, false>>;
|
|
109
|
-
// `required` is the one key that stayed behind, and deliberately so
|
|
110
|
-
// (objectui#3222): the required marker has a single author — the form
|
|
111
|
-
// renderer's `<FormLabel>` — and lowering the flag into widget props gives
|
|
112
|
-
// it a second, i.e. the double-display failure that also keeps the
|
|
113
|
-
// validation TEXT out of the widget. The a11y state a widget could
|
|
114
|
-
// legitimately carry is `aria-required`, and `AriaAttributes` already
|
|
115
|
-
// supplies that key with no contract change at all.
|
|
116
|
-
type _RequiredIsAbsent = Assert<Equal<HasKey<FieldWidgetComponentProps, 'required'>, false>>;
|
|
117
|
-
|
|
118
|
-
// `data-*` stays open (it is open in HTML too), but as a template-literal
|
|
119
|
-
// key — the distinction that keeps `keyof` finite above. A pin, because
|
|
120
|
-
// widening it back to `[key: string]` would silently restore the defect
|
|
121
|
-
// while every assertion here still read as "closed".
|
|
122
|
-
type _DataAttributesStayOpen = Assert<Extends<'data-testid', keyof FieldWidgetComponentProps>>;
|
|
123
|
-
|
|
124
|
-
expect(true).toBe(true);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
@@ -1,361 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ObjectUI
|
|
3
|
-
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
-
*
|
|
5
|
-
* This source code is licensed under the MIT license found in the
|
|
6
|
-
* LICENSE file in the root directory of this source tree.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* P3.2 Field Widget Polish - Validation Feedback
|
|
11
|
-
*
|
|
12
|
-
* Tests that field widgets consistently handle validation feedback:
|
|
13
|
-
* `error` prop, aria-invalid attribute, and disabled states.
|
|
14
|
-
*
|
|
15
|
-
* The slot is `error` — the name `@objectstack/spec/ui`'s
|
|
16
|
-
* `FieldWidgetPropsSchema` gives it — since objectui#3222. These are the
|
|
17
|
-
* CONSUMER half only: they prove a widget handed a message marks its control
|
|
18
|
-
* invalid. That was true before #3222 too, under the name `errorMessage`, and
|
|
19
|
-
* it did not help anyone, because no host ever passed the prop. The PRODUCER
|
|
20
|
-
* half is `form-error-delivery.test.tsx` in `@object-ui/components` plus
|
|
21
|
-
* `widget-aria-invalid-e2e.test.tsx` next to this file; a rename alone would
|
|
22
|
-
* have swapped one dead key for another.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
26
|
-
import React from 'react';
|
|
27
|
-
import { render, screen } from '@testing-library/react';
|
|
28
|
-
import '@testing-library/jest-dom';
|
|
29
|
-
|
|
30
|
-
import { TextField } from '../widgets/TextField';
|
|
31
|
-
import { NumberField } from '../widgets/NumberField';
|
|
32
|
-
import { EmailField } from '../widgets/EmailField';
|
|
33
|
-
import { UrlField } from '../widgets/UrlField';
|
|
34
|
-
import { PhoneField } from '../widgets/PhoneField';
|
|
35
|
-
import { TextAreaField } from '../widgets/TextAreaField';
|
|
36
|
-
import { CurrencyField } from '../widgets/CurrencyField';
|
|
37
|
-
import { BooleanField } from '../widgets/BooleanField';
|
|
38
|
-
import { SelectField } from '../widgets/SelectField';
|
|
39
|
-
|
|
40
|
-
const noop = vi.fn();
|
|
41
|
-
|
|
42
|
-
describe('P3.2 Validation Feedback', () => {
|
|
43
|
-
// ---------------------------------------------------------------
|
|
44
|
-
// aria-invalid on `error`
|
|
45
|
-
// ---------------------------------------------------------------
|
|
46
|
-
describe('aria-invalid attribute', () => {
|
|
47
|
-
it('EmailField sets aria-invalid when `error` provided', () => {
|
|
48
|
-
render(
|
|
49
|
-
<EmailField
|
|
50
|
-
value=""
|
|
51
|
-
onChange={noop}
|
|
52
|
-
field={{ type: 'email' } as any}
|
|
53
|
-
error="Invalid email"
|
|
54
|
-
/>
|
|
55
|
-
);
|
|
56
|
-
const input = screen.getByRole('textbox');
|
|
57
|
-
expect(input).toHaveAttribute('aria-invalid', 'true');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('EmailField does not set aria-invalid without `error`', () => {
|
|
61
|
-
render(
|
|
62
|
-
<EmailField
|
|
63
|
-
value=""
|
|
64
|
-
onChange={noop}
|
|
65
|
-
field={{ type: 'email' } as any}
|
|
66
|
-
/>
|
|
67
|
-
);
|
|
68
|
-
const input = screen.getByRole('textbox');
|
|
69
|
-
expect(input).not.toHaveAttribute('aria-invalid', 'true');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('UrlField sets aria-invalid when `error` provided', () => {
|
|
73
|
-
render(
|
|
74
|
-
<UrlField
|
|
75
|
-
value=""
|
|
76
|
-
onChange={noop}
|
|
77
|
-
field={{ type: 'url' } as any}
|
|
78
|
-
error="Invalid URL"
|
|
79
|
-
/>
|
|
80
|
-
);
|
|
81
|
-
const input = screen.getByDisplayValue('');
|
|
82
|
-
expect(input).toHaveAttribute('aria-invalid', 'true');
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('PhoneField sets aria-invalid when `error` provided', () => {
|
|
86
|
-
render(
|
|
87
|
-
<PhoneField
|
|
88
|
-
value=""
|
|
89
|
-
onChange={noop}
|
|
90
|
-
field={{ type: 'phone' } as any}
|
|
91
|
-
error="Invalid phone"
|
|
92
|
-
/>
|
|
93
|
-
);
|
|
94
|
-
const input = screen.getByRole('textbox');
|
|
95
|
-
expect(input).toHaveAttribute('aria-invalid', 'true');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('TextAreaField sets aria-invalid when `error` provided', () => {
|
|
99
|
-
render(
|
|
100
|
-
<TextAreaField
|
|
101
|
-
value=""
|
|
102
|
-
onChange={noop}
|
|
103
|
-
field={{ type: 'textarea' } as any}
|
|
104
|
-
error="Required"
|
|
105
|
-
/>
|
|
106
|
-
);
|
|
107
|
-
const textarea = screen.getByRole('textbox');
|
|
108
|
-
expect(textarea).toHaveAttribute('aria-invalid', 'true');
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('CurrencyField sets aria-invalid when `error` provided', () => {
|
|
112
|
-
render(
|
|
113
|
-
<CurrencyField
|
|
114
|
-
value={0}
|
|
115
|
-
onChange={noop}
|
|
116
|
-
field={{ type: 'currency' } as any}
|
|
117
|
-
error="Must be positive"
|
|
118
|
-
/>
|
|
119
|
-
);
|
|
120
|
-
const input = screen.getByRole('spinbutton');
|
|
121
|
-
expect(input).toHaveAttribute('aria-invalid', 'true');
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
// ---------------------------------------------------------------
|
|
126
|
-
// Disabled state consistency
|
|
127
|
-
// ---------------------------------------------------------------
|
|
128
|
-
describe('disabled state', () => {
|
|
129
|
-
it('TextField supports disabled prop', () => {
|
|
130
|
-
render(
|
|
131
|
-
<TextField
|
|
132
|
-
value="test"
|
|
133
|
-
onChange={noop}
|
|
134
|
-
field={{ type: 'text' } as any}
|
|
135
|
-
disabled={true}
|
|
136
|
-
/>
|
|
137
|
-
);
|
|
138
|
-
expect(screen.getByRole('textbox')).toBeDisabled();
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('NumberField supports disabled prop', () => {
|
|
142
|
-
render(
|
|
143
|
-
<NumberField
|
|
144
|
-
value={5}
|
|
145
|
-
onChange={noop}
|
|
146
|
-
field={{ type: 'number' } as any}
|
|
147
|
-
disabled={true}
|
|
148
|
-
/>
|
|
149
|
-
);
|
|
150
|
-
expect(screen.getByRole('spinbutton')).toBeDisabled();
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('EmailField supports disabled prop', () => {
|
|
154
|
-
render(
|
|
155
|
-
<EmailField
|
|
156
|
-
value=""
|
|
157
|
-
onChange={noop}
|
|
158
|
-
field={{ type: 'email' } as any}
|
|
159
|
-
disabled={true}
|
|
160
|
-
/>
|
|
161
|
-
);
|
|
162
|
-
expect(screen.getByRole('textbox')).toBeDisabled();
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('PhoneField supports disabled prop', () => {
|
|
166
|
-
render(
|
|
167
|
-
<PhoneField
|
|
168
|
-
value=""
|
|
169
|
-
onChange={noop}
|
|
170
|
-
field={{ type: 'phone' } as any}
|
|
171
|
-
disabled={true}
|
|
172
|
-
/>
|
|
173
|
-
);
|
|
174
|
-
expect(screen.getByRole('textbox')).toBeDisabled();
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it('TextAreaField supports disabled prop', () => {
|
|
178
|
-
render(
|
|
179
|
-
<TextAreaField
|
|
180
|
-
value=""
|
|
181
|
-
onChange={noop}
|
|
182
|
-
field={{ type: 'textarea' } as any}
|
|
183
|
-
disabled={true}
|
|
184
|
-
/>
|
|
185
|
-
);
|
|
186
|
-
expect(screen.getByRole('textbox')).toBeDisabled();
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
// ---------------------------------------------------------------
|
|
191
|
-
// Readonly mode consistency
|
|
192
|
-
// ---------------------------------------------------------------
|
|
193
|
-
describe('readonly mode output', () => {
|
|
194
|
-
it('TextField shows dash for empty value in readonly', () => {
|
|
195
|
-
const { container } = render(
|
|
196
|
-
<TextField
|
|
197
|
-
value=""
|
|
198
|
-
onChange={noop}
|
|
199
|
-
field={{ type: 'text' } as any}
|
|
200
|
-
readonly={true}
|
|
201
|
-
/>
|
|
202
|
-
);
|
|
203
|
-
expect(container.textContent).toBe('—');
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
it('NumberField shows dash for null value in readonly', () => {
|
|
207
|
-
const { container } = render(
|
|
208
|
-
<NumberField
|
|
209
|
-
value={null as any}
|
|
210
|
-
onChange={noop}
|
|
211
|
-
field={{ type: 'number' } as any}
|
|
212
|
-
readonly={true}
|
|
213
|
-
/>
|
|
214
|
-
);
|
|
215
|
-
expect(container.textContent).toBe('—');
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it('EmailField shows dash for empty value in readonly', () => {
|
|
219
|
-
const { container } = render(
|
|
220
|
-
<EmailField
|
|
221
|
-
value=""
|
|
222
|
-
onChange={noop}
|
|
223
|
-
field={{ type: 'email' } as any}
|
|
224
|
-
readonly={true}
|
|
225
|
-
/>
|
|
226
|
-
);
|
|
227
|
-
expect(container.textContent).toBe('—');
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it('UrlField shows dash for empty value in readonly', () => {
|
|
231
|
-
const { container } = render(
|
|
232
|
-
<UrlField
|
|
233
|
-
value=""
|
|
234
|
-
onChange={noop}
|
|
235
|
-
field={{ type: 'url' } as any}
|
|
236
|
-
readonly={true}
|
|
237
|
-
/>
|
|
238
|
-
);
|
|
239
|
-
expect(container.textContent).toBe('—');
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it('PhoneField shows dash for empty value in readonly', () => {
|
|
243
|
-
const { container } = render(
|
|
244
|
-
<PhoneField
|
|
245
|
-
value=""
|
|
246
|
-
onChange={noop}
|
|
247
|
-
field={{ type: 'phone' } as any}
|
|
248
|
-
readonly={true}
|
|
249
|
-
/>
|
|
250
|
-
);
|
|
251
|
-
expect(container.textContent).toBe('—');
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
it('TextAreaField shows dash for empty value in readonly', () => {
|
|
255
|
-
const { container } = render(
|
|
256
|
-
<TextAreaField
|
|
257
|
-
value=""
|
|
258
|
-
onChange={noop}
|
|
259
|
-
field={{ type: 'textarea' } as any}
|
|
260
|
-
readonly={true}
|
|
261
|
-
/>
|
|
262
|
-
);
|
|
263
|
-
expect(container.textContent).toBe('—');
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
it('BooleanField shows Yes/No in readonly', () => {
|
|
267
|
-
const { container: yesC } = render(
|
|
268
|
-
<BooleanField
|
|
269
|
-
value={true}
|
|
270
|
-
onChange={noop}
|
|
271
|
-
field={{ type: 'boolean' } as any}
|
|
272
|
-
readonly={true}
|
|
273
|
-
/>
|
|
274
|
-
);
|
|
275
|
-
expect(yesC.textContent).toBe('Yes');
|
|
276
|
-
|
|
277
|
-
const { container: noC } = render(
|
|
278
|
-
<BooleanField
|
|
279
|
-
value={false}
|
|
280
|
-
onChange={noop}
|
|
281
|
-
field={{ type: 'boolean' } as any}
|
|
282
|
-
readonly={true}
|
|
283
|
-
/>
|
|
284
|
-
);
|
|
285
|
-
expect(noC.textContent).toBe('No');
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
it('SelectField shows dash for empty value in readonly', () => {
|
|
289
|
-
const { container } = render(
|
|
290
|
-
<SelectField
|
|
291
|
-
value=""
|
|
292
|
-
onChange={noop}
|
|
293
|
-
field={{ type: 'select', options: [] } as any}
|
|
294
|
-
readonly={true}
|
|
295
|
-
/>
|
|
296
|
-
);
|
|
297
|
-
expect(container.textContent).toBe('—');
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
it('CurrencyField shows dash for null value in readonly', () => {
|
|
301
|
-
const { container } = render(
|
|
302
|
-
<CurrencyField
|
|
303
|
-
value={null as any}
|
|
304
|
-
onChange={noop}
|
|
305
|
-
field={{ type: 'currency' } as any}
|
|
306
|
-
readonly={true}
|
|
307
|
-
/>
|
|
308
|
-
);
|
|
309
|
-
expect(container.textContent).toBe('—');
|
|
310
|
-
});
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
// ---------------------------------------------------------------
|
|
314
|
-
// Placeholder consistency
|
|
315
|
-
// ---------------------------------------------------------------
|
|
316
|
-
describe('placeholder support', () => {
|
|
317
|
-
it('TextField uses field placeholder', () => {
|
|
318
|
-
render(
|
|
319
|
-
<TextField
|
|
320
|
-
value=""
|
|
321
|
-
onChange={noop}
|
|
322
|
-
field={{ type: 'text', placeholder: 'Enter name...' } as any}
|
|
323
|
-
/>
|
|
324
|
-
);
|
|
325
|
-
expect(screen.getByPlaceholderText('Enter name...')).toBeInTheDocument();
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
it('EmailField uses default placeholder', () => {
|
|
329
|
-
render(
|
|
330
|
-
<EmailField
|
|
331
|
-
value=""
|
|
332
|
-
onChange={noop}
|
|
333
|
-
field={{ type: 'email' } as any}
|
|
334
|
-
/>
|
|
335
|
-
);
|
|
336
|
-
expect(screen.getByPlaceholderText('email@example.com')).toBeInTheDocument();
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
it('UrlField uses default placeholder', () => {
|
|
340
|
-
render(
|
|
341
|
-
<UrlField
|
|
342
|
-
value=""
|
|
343
|
-
onChange={noop}
|
|
344
|
-
field={{ type: 'url' } as any}
|
|
345
|
-
/>
|
|
346
|
-
);
|
|
347
|
-
expect(screen.getByPlaceholderText('https://example.com')).toBeInTheDocument();
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
it('PhoneField uses default placeholder', () => {
|
|
351
|
-
render(
|
|
352
|
-
<PhoneField
|
|
353
|
-
value=""
|
|
354
|
-
onChange={noop}
|
|
355
|
-
field={{ type: 'phone' } as any}
|
|
356
|
-
/>
|
|
357
|
-
);
|
|
358
|
-
expect(screen.getByPlaceholderText('(555) 123-4567')).toBeInTheDocument();
|
|
359
|
-
});
|
|
360
|
-
});
|
|
361
|
-
});
|