@remoteoss/json-schema-form 0.11.1-dev.20240726080523 → 0.11.2-beta.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 CHANGED
@@ -1,3 +1,22 @@
1
+ #### 0.11.2-beta.0 (2024-07-30)
2
+
3
+ ##### Bug Fixes
4
+
5
+ - **modify:**
6
+
7
+ - Support x-jsf shorthands in all methods, fix TS declarations; ([#85](https://github.com/remoteoss/json-schema-form/pull/85)) ([9a87c19b](https://github.com/remoteoss/json-schema-form/commit/9a87c19b6d373a8f660dd24b20a48725f3dbe8d0))
8
+
9
+ - **createHeadlessForm:**
10
+
11
+ - mark createHeadlessForm config.customProperties as deprecated ([#85](https://github.com/remoteoss/json-schema-form/pull/85)) ([9a87c19b](https://github.com/remoteoss/json-schema-form/commit/9a87c19b6d373a8f660dd24b20a48725f3dbe8d0))
12
+ - support custom attributes as functions (do not execute them) ([#86](https://github.com/remoteoss/json-schema-form/pull/86)) ([3674fc87](https://github.com/remoteoss/json-schema-form/commit/3674fc87d44df44a5ba9b13b40465c2ccda448ca))
13
+
14
+ #### 0.11.1-beta.0 (2024-07-26)
15
+
16
+ ##### Chores
17
+
18
+ - **modify:** Add missing Typescript declarations ([#84](https://github.com/remoteoss/json-schema-form/pull/84)) ([86b3af2a](https://github.com/remoteoss/json-schema-form/commit/86b3af2a31981718ad349ac8d08fbdce3aa86acf))
19
+
1
20
  #### 0.11.0-beta.0 (2024-07-25)
2
21
 
3
22
  ##### Breaking changes
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
 
2
2
  /*!
3
3
  Copyright (c) 2024 Remote Technology, Inc.
4
- NPM Package: @remoteoss/json-schema-form@0.11.1-dev.20240726080523
5
- Generated: Fri, 26 Jul 2024 08:05:36 GMT
4
+ NPM Package: @remoteoss/json-schema-form@0.11.2-beta.0
5
+ Generated: Tue, 30 Jul 2024 12:00:17 GMT
6
6
 
7
7
  MIT License
8
8
 
@@ -1196,7 +1196,10 @@ function updateField(field, requiredFields, node, formValues, logic, config) {
1196
1196
  }
1197
1197
  const updateAttributes = (fieldAttrs) => {
1198
1198
  Object.entries(fieldAttrs).forEach(([key, value]) => {
1199
- field[key] = typeof value === "function" ? value() : value;
1199
+ field[key] = value;
1200
+ if (key === "schema" && typeof value === "function") {
1201
+ field[key] = value();
1202
+ }
1200
1203
  if (key === "value") {
1201
1204
  const readOnlyPropertyWasUpdated = typeof fieldAttrs.readOnly !== "undefined";
1202
1205
  const isReadonlyByDefault = field.readOnly;
@@ -1917,7 +1920,7 @@ function rewriteAllFields(schema, configCallback, context) {
1917
1920
  (0, import_get4.default)(schema.properties, fieldName),
1918
1921
  {
1919
1922
  ...fieldAttrs,
1920
- ...configCallback(fullName, fieldAttrs)
1923
+ ...standardizeAttrs(configCallback(fullName, fieldAttrs))
1921
1924
  },
1922
1925
  mergeReplaceArray
1923
1926
  );
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
 
2
2
  /*!
3
3
  Copyright (c) 2024 Remote Technology, Inc.
4
- NPM Package: @remoteoss/json-schema-form@0.11.1-dev.20240726080523
5
- Generated: Fri, 26 Jul 2024 08:05:36 GMT
4
+ NPM Package: @remoteoss/json-schema-form@0.11.2-beta.0
5
+ Generated: Tue, 30 Jul 2024 12:00:17 GMT
6
6
 
7
7
  MIT License
8
8
 
@@ -1159,7 +1159,10 @@ function updateField(field, requiredFields, node, formValues, logic, config) {
1159
1159
  }
1160
1160
  const updateAttributes = (fieldAttrs) => {
1161
1161
  Object.entries(fieldAttrs).forEach(([key, value]) => {
1162
- field[key] = typeof value === "function" ? value() : value;
1162
+ field[key] = value;
1163
+ if (key === "schema" && typeof value === "function") {
1164
+ field[key] = value();
1165
+ }
1163
1166
  if (key === "value") {
1164
1167
  const readOnlyPropertyWasUpdated = typeof fieldAttrs.readOnly !== "undefined";
1165
1168
  const isReadonlyByDefault = field.readOnly;
@@ -1880,7 +1883,7 @@ function rewriteAllFields(schema, configCallback, context) {
1880
1883
  get4(schema.properties, fieldName),
1881
1884
  {
1882
1885
  ...fieldAttrs,
1883
- ...configCallback(fullName, fieldAttrs)
1886
+ ...standardizeAttrs(configCallback(fullName, fieldAttrs))
1884
1887
  },
1885
1888
  mergeReplaceArray
1886
1889
  );
@@ -1,8 +1,8 @@
1
1
 
2
2
  /*!
3
3
  Copyright (c) 2024 Remote Technology, Inc.
4
- NPM Package: @remoteoss/json-schema-form@0.11.1-dev.20240726080523
5
- Generated: Fri, 26 Jul 2024 08:05:36 GMT
4
+ NPM Package: @remoteoss/json-schema-form@0.11.2-beta.0
5
+ Generated: Tue, 30 Jul 2024 12:00:17 GMT
6
6
 
7
7
  MIT License
8
8
 
@@ -12675,7 +12675,10 @@ function updateField(field, requiredFields, node, formValues, logic, config) {
12675
12675
  }
12676
12676
  const updateAttributes = (fieldAttrs) => {
12677
12677
  Object.entries(fieldAttrs).forEach(([key, value]) => {
12678
- field[key] = typeof value === "function" ? value() : value;
12678
+ field[key] = value;
12679
+ if (key === "schema" && typeof value === "function") {
12680
+ field[key] = value();
12681
+ }
12679
12682
  if (key === "value") {
12680
12683
  const readOnlyPropertyWasUpdated = typeof fieldAttrs.readOnly !== "undefined";
12681
12684
  const isReadonlyByDefault = field.readOnly;
@@ -13396,7 +13399,7 @@ function rewriteAllFields(schema, configCallback, context) {
13396
13399
  (0, import_get5.default)(schema.properties, fieldName),
13397
13400
  {
13398
13401
  ...fieldAttrs,
13399
- ...configCallback(fullName, fieldAttrs)
13402
+ ...standardizeAttrs(configCallback(fullName, fieldAttrs))
13400
13403
  },
13401
13404
  mergeReplaceArray
13402
13405
  );
@@ -1,5 +1,3 @@
1
- import { WARNING_TYPES } from './src/modify';
2
-
3
1
  /**
4
2
  * Shorthand to lookup for keys with `x-jsf-*` preffix.
5
3
  */
@@ -108,28 +106,26 @@ type JSONSchemaObjectType = Record<string, unknown>;
108
106
  type FieldName = string;
109
107
  type FieldAttrs = Record<string, unknown>;
110
108
 
111
- type FieldAttrsCallbackFn = (fieldName: FieldName, fieldAttrs: FieldAttrs) => FieldAttrs;
112
-
113
- type ModConfigProperties = Record<FieldName, FieldAttrs | FieldAttrsCallbackFn>;
114
-
115
109
  type ModifyConfig = {
116
110
  /**
117
111
  * An object with the fields to be modified with the returned attributes.
118
112
  */
119
- fields?: ModConfigProperties;
113
+ fields?: Record<FieldName, FieldAttrs | ((fieldAttrs: FieldAttrs) => FieldAttrs)>;
120
114
  /**
121
115
  * A callback function that runs through all fields, to modify them with the returned attributes.
122
116
  */
123
- allFields?: FieldAttrsCallbackFn;
117
+ allFields?: (fieldName: FieldName, fieldAttrs: FieldAttrs) => FieldAttrs;
124
118
  /**
125
119
  * An object of new fields to be created with the new attributes.
126
120
  */
127
- create?: ModConfigProperties;
121
+ create?: Record<FieldName, FieldAttrs>;
128
122
  /**
129
123
  * An array of the fields to keep, removing the remaining fields.
130
124
  */
131
125
  pick?: FieldName[];
132
- /* An array of fieldNames with the new order. Can be an object or callback function */
126
+ /**
127
+ * An array of fieldNames with the new order. Can be an object or callback function
128
+ * */
133
129
  orderRoot?: FieldName[] | ((originalOrder: FieldName[]) => FieldName[]);
134
130
  /**
135
131
  * Mutes any logs or warnings from the library.
@@ -137,15 +133,23 @@ type ModifyConfig = {
137
133
  muteLogging?: boolean;
138
134
  };
139
135
 
140
- const warningTypes = WARNING_TYPES satisfies Record<string, string>;
141
-
142
- type WARNING_TYPES = keyof typeof warningTypes;
136
+ type WarningType =
137
+ | 'FIELD_TO_CHANGE_NOT_FOUND'
138
+ | 'ORDER_MISSING_FIELDS'
139
+ | 'FIELD_TO_CREATE_EXISTS'
140
+ | 'PICK_MISSED_FIELD';
143
141
 
144
- type Warnings = {
142
+ type Warning = {
145
143
  message: string;
146
- type: WARNING_TYPES;
144
+ type: WarningType;
147
145
  meta?: Record<string, unknown>;
148
- }[];
146
+ };
147
+
148
+ type ModifyOutput = {
149
+ schema: JSONSchemaObjectType;
150
+ warnings: Warning[];
151
+ };
152
+
149
153
  /**
150
154
  * Generates the Headless form based on the provided JSON schema
151
155
  */
@@ -156,18 +160,16 @@ export function createHeadlessForm(
156
160
  ): HeadlessFormOutput;
157
161
 
158
162
  /**
159
- * Modifies the JSON schema based on the provided configuration.
163
+ * Returns a new JSON Schema modified based a given configuration.
164
+ * This does not mutate the original JSON Schema.
160
165
  */
161
166
  export function modify(
162
167
  /**
163
- * The original JSON schema to be modified.
168
+ * The original JSON schema as base to the modifications.
164
169
  */
165
170
  originalSchema: JSONSchemaObjectType,
166
171
  /**
167
- * The configuration object containing the fields and allFields properties.
172
+ * The configuration object to create a new JSON Schema based on the originalSchema.
168
173
  */
169
174
  config?: ModifyConfig
170
- ): {
171
- schema: JSONSchemaObjectType;
172
- warnings: Warnings;
173
- };
175
+ ): ModifyOutput;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remoteoss/json-schema-form",
3
- "version": "0.11.1-dev.20240726080523",
3
+ "version": "0.11.2-beta.0",
4
4
  "description": "Headless UI form powered by JSON Schemas",
5
5
  "author": "Remote.com <engineering@remote.com> (https://remote.com/)",
6
6
  "license": "MIT",
@@ -2177,6 +2177,75 @@ describe('createHeadlessForm', () => {
2177
2177
  });
2178
2178
  });
2179
2179
 
2180
+ it('pass custom attributes as function', () => {
2181
+ function FakeComponent(props) {
2182
+ const { label, description } = props;
2183
+ return `A React component with ${label} and ${description}`;
2184
+ }
2185
+ // Any custom attributes must be inside "x-jsf-presentation"
2186
+ const { fields, handleValidation } = createHeadlessForm({
2187
+ properties: {
2188
+ field_a: {
2189
+ title: 'Field A',
2190
+ 'x-jsf-presentation': {
2191
+ inputType: 'text',
2192
+ MyComponent: FakeComponent,
2193
+ },
2194
+ },
2195
+ field_b: {
2196
+ title: 'Field B',
2197
+ 'x-jsf-presentation': {
2198
+ inputType: 'text',
2199
+ MyComponent: FakeComponent,
2200
+ },
2201
+ },
2202
+ },
2203
+ allOf: [
2204
+ {
2205
+ if: {
2206
+ properties: {
2207
+ field_a: { const: 'yes' },
2208
+ },
2209
+ required: ['field_a'],
2210
+ },
2211
+ then: {
2212
+ required: ['field_b'],
2213
+ },
2214
+ },
2215
+ ],
2216
+ });
2217
+
2218
+ const fieldA = getField(fields, 'field_a');
2219
+ expect(fieldA).toMatchObject({
2220
+ label: 'Field A',
2221
+ MyComponent: expect.any(Function),
2222
+ });
2223
+
2224
+ const fieldB = getField(fields, 'field_b');
2225
+ expect(fieldB).toMatchObject({
2226
+ label: 'Field B',
2227
+ required: false,
2228
+ MyComponent: expect.any(Function),
2229
+ });
2230
+
2231
+ const fakeProps = { label: 'Field B', description: 'fake description' };
2232
+ expect(fieldB.MyComponent(fakeProps)).toBe(
2233
+ 'A React component with Field B and fake description'
2234
+ );
2235
+
2236
+ // Ensure "MyComponent" attribute still exsits after a validation cycle.
2237
+ // This covers the updateField(). Check PR for more context.
2238
+ handleValidation({ field_a: 'yes' });
2239
+
2240
+ expect(getField(fields, 'field_a')).toMatchObject({
2241
+ MyComponent: expect.any(Function),
2242
+ });
2243
+ expect(getField(fields, 'field_b')).toMatchObject({
2244
+ required: true,
2245
+ MyComponent: expect.any(Function),
2246
+ });
2247
+ });
2248
+
2180
2249
  it('passes scopedJsonSchema to each field', () => {
2181
2250
  const { fields } = createHeadlessForm(schemaWithoutInputTypes, {
2182
2251
  strictInputType: false,
@@ -358,40 +358,94 @@ describe('modify() - basic mutations', () => {
358
358
  },
359
359
  });
360
360
  });
361
+ });
361
362
 
362
- it('customize x-jsf-errorMessage (shorthand)', () => {
363
- const result = modify(schemaPet, {
364
- fields: {
365
- pet_age: (fieldAttrs) => {
366
- return {
367
- errorMessage: {
368
- minimum: `We only accept pets up to ${fieldAttrs.maximum} months old.`,
369
- required: 'We need to know your pet name.',
363
+ describe('supporting shorthands', () => {
364
+ const invoiceSchema = {
365
+ properties: {
366
+ title: {
367
+ title: 'Invoice title',
368
+ 'x-jsf-presentation': {
369
+ inputType: 'text',
370
+ },
371
+ 'x-jsf-errorMessage': {
372
+ required: 'Cannot be empty.',
373
+ },
374
+ type: 'string',
375
+ },
376
+ total: {
377
+ title: 'Invoice amount',
378
+ 'x-jsf-presentation': {
379
+ inputType: 'money',
380
+ },
381
+ type: 'number',
382
+ },
383
+ taxes: {
384
+ title: 'Taxes details',
385
+ properties: {
386
+ country: {
387
+ title: 'Country',
388
+ 'x-jsf-presentation': {
389
+ inputType: 'country',
370
390
  },
371
- };
391
+ type: 'string',
392
+ },
393
+ percentage: {
394
+ title: 'Percentage',
395
+ 'x-jsf-presentation': {
396
+ inputType: 'number',
397
+ },
398
+ },
399
+ type: 'integer',
372
400
  },
373
- 'pet_address.street': {
401
+ },
402
+ },
403
+ required: ['title'],
404
+ };
405
+
406
+ it('basic support for x-jsf-presentation and x-jsf-errorMessage in config.fields', () => {
407
+ const result = modify(invoiceSchema, {
408
+ fields: {
409
+ title: {
374
410
  errorMessage: {
375
- required: 'Your pet cannot live in the street.',
411
+ maxLength: 'Must be shorter.',
412
+ },
413
+ presentation: {
414
+ dataFoo: 123,
415
+ },
416
+ },
417
+ 'taxes.country': {
418
+ errorMessage: {
419
+ required: 'The country is required.',
420
+ },
421
+ presentation: {
422
+ flags: true,
376
423
  },
377
424
  },
378
425
  },
379
- // ...
380
426
  });
381
427
 
428
+ // Assert all the other propreties are kept
429
+ expect(result.schema).toMatchObject(invoiceSchema);
430
+
382
431
  expect(result.schema).toMatchObject({
383
432
  properties: {
384
- pet_age: {
433
+ title: {
385
434
  'x-jsf-errorMessage': {
386
- minimum: `We only accept pets up to 24 months old.`,
387
- required: 'We need to know your pet name.',
435
+ maxLength: 'Must be shorter.',
436
+ },
437
+ 'x-jsf-presentation': {
438
+ dataFoo: 123,
388
439
  },
389
440
  },
390
- pet_address: {
441
+ taxes: {
391
442
  properties: {
392
- street: {
443
+ country: {
393
444
  'x-jsf-errorMessage': {
394
- required: 'Your pet cannot live in the street.',
445
+ required: 'The country is required.',
446
+ },
447
+ 'x-jsf-presentation': {
448
+ flags: true,
395
449
  },
396
450
  },
397
451
  },
@@ -400,47 +454,90 @@ describe('modify() - basic mutations', () => {
400
454
  });
401
455
  });
402
456
 
403
- it('customize x-jsf-presentation (shorthand)', () => {
404
- const result = modify(schemaPet, {
405
- fields: {
406
- pet_age: () => {
407
- return {
408
- presentation: {
409
- 'data-foo': 123,
410
- },
411
- };
457
+ it('shorthands work in config.allFields', () => {
458
+ const result = modify(invoiceSchema, {
459
+ allFields: (fieldName, attrs) => ({
460
+ errorMessage: {
461
+ required: `${attrs.title} cannot be empty.`,
412
462
  },
413
- 'pet_address.street': {
414
- presentation: {
415
- 'data-foo': 456,
416
- },
463
+ presentation: {
464
+ dataName: fieldName,
417
465
  },
418
- },
466
+ }),
419
467
  });
420
468
 
421
- const originalPetAgePresentation = schemaPet.properties.pet_age['x-jsf-presentation'];
422
- expect(originalPetAgePresentation).toBeDefined();
423
-
424
- const originalPetStreetPresentation =
425
- schemaPet.properties.pet_address.properties.street['x-jsf-presentation'];
426
- expect(originalPetStreetPresentation).toBeDefined();
427
-
428
469
  expect(result.schema).toMatchObject({
429
470
  properties: {
430
- pet_age: {
471
+ title: {
472
+ 'x-jsf-errorMessage': {
473
+ required: 'Invoice title cannot be empty.',
474
+ },
431
475
  'x-jsf-presentation': {
432
- ...originalPetAgePresentation,
433
- 'data-foo': 123,
476
+ dataName: 'title',
434
477
  },
435
478
  },
436
- pet_address: {
479
+ total: {
480
+ 'x-jsf-errorMessage': {
481
+ required: 'Invoice amount cannot be empty.',
482
+ },
483
+ 'x-jsf-presentation': {
484
+ dataName: 'total',
485
+ },
486
+ },
487
+ taxes: {
488
+ 'x-jsf-errorMessage': {
489
+ required: 'Taxes details cannot be empty.',
490
+ },
491
+ 'x-jsf-presentation': {
492
+ dataName: 'taxes',
493
+ },
437
494
  properties: {
438
- street: {
495
+ country: {
496
+ 'x-jsf-errorMessage': {
497
+ required: 'Country cannot be empty.',
498
+ },
439
499
  'x-jsf-presentation': {
440
- ...originalPetStreetPresentation,
441
- 'data-foo': 456,
500
+ dataName: 'taxes.country',
442
501
  },
443
502
  },
503
+ percentage: {
504
+ 'x-jsf-errorMessage': {
505
+ required: 'Percentage cannot be empty.',
506
+ },
507
+ 'x-jsf-presentation': {
508
+ dataName: 'taxes.percentage',
509
+ },
510
+ },
511
+ },
512
+ },
513
+ },
514
+ });
515
+ });
516
+
517
+ it('shorthands work in config.create', () => {
518
+ const result = modify(invoiceSchema, {
519
+ create: {
520
+ invoice_id: {
521
+ title: 'Invoice ID',
522
+ presentation: {
523
+ inputType: 'text',
524
+ },
525
+ errorMessage: {
526
+ pattern: 'Must have 5 characters.',
527
+ },
528
+ },
529
+ },
530
+ });
531
+
532
+ expect(result.schema).toMatchObject({
533
+ properties: {
534
+ invoice_id: {
535
+ title: 'Invoice ID',
536
+ 'x-jsf-presentation': {
537
+ inputType: 'text',
538
+ },
539
+ 'x-jsf-errorMessage': {
540
+ pattern: 'Must have 5 characters.',
444
541
  },
445
542
  },
446
543
  },