@abgov/jsonforms-components 2.5.0 → 2.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/index.esm.js CHANGED
@@ -7482,8 +7482,6 @@ const FormStepperReviewer = props => {
7482
7482
  };
7483
7483
  const FormStepperReviewControl = withAjvProps(withTranslateProps(withJsonFormsLayoutProps(FormStepperReviewer)));
7484
7484
 
7485
- const TABLE_CONTEXT_ID = 100001;
7486
-
7487
7485
  const isErrorPathIncluded = (errorPaths, path) => {
7488
7486
  return errorPaths.some(ePath => {
7489
7487
  /**
@@ -7576,16 +7574,15 @@ const stepperReducer = (state, action) => {
7576
7574
  id
7577
7575
  } = action.payload;
7578
7576
  state.activeId = id;
7579
- if (id === TABLE_CONTEXT_ID) {
7580
- return Object.assign({}, state);
7581
- }
7582
7577
  if (id === lastId + 1) {
7583
7578
  state.isOnReview = true;
7584
7579
  state.hasNextButton = false;
7585
7580
  state.hasPrevButton = true;
7586
7581
  return Object.assign({}, state);
7587
7582
  } else {
7588
- state.categories[id].isVisited = true;
7583
+ if (state.categories[id]) {
7584
+ state.categories[id].isVisited = true;
7585
+ }
7589
7586
  state.hasNextButton = id <= lastId;
7590
7587
  state.hasPrevButton = id !== 0;
7591
7588
  state.isOnReview = false;
@@ -7600,7 +7597,7 @@ const stepperReducer = (state, action) => {
7600
7597
  ajv,
7601
7598
  errors
7602
7599
  } = action.payload;
7603
- if (id === state.categories.length || id === TABLE_CONTEXT_ID) {
7600
+ if (id === state.categories.length || id === state.categories.length + 1) {
7604
7601
  return Object.assign({}, state);
7605
7602
  }
7606
7603
  /*
@@ -7608,9 +7605,11 @@ const stepperReducer = (state, action) => {
7608
7605
  */
7609
7606
  const incompletePaths = getIncompletePaths(ajv, ((_a = state.categories[id]) === null || _a === void 0 ? void 0 : _a.scopes) || []);
7610
7607
  const errorsInCategory = getErrorsInScopes(errors, ((_b = state.categories[id]) === null || _b === void 0 ? void 0 : _b.scopes) || []);
7611
- state.categories[id].isCompleted = (incompletePaths === null || incompletePaths === void 0 ? void 0 : incompletePaths.length) === 0;
7612
- state.categories[id].isValid = errorsInCategory.length === 0;
7613
- state.categories[id].isVisited = true;
7608
+ if (state.categories[id]) {
7609
+ state.categories[id].isCompleted = (incompletePaths === null || incompletePaths === void 0 ? void 0 : incompletePaths.length) === 0;
7610
+ state.categories[id].isValid = errorsInCategory.length === 0;
7611
+ state.categories[id].isVisited = true;
7612
+ }
7614
7613
  return Object.assign({}, state);
7615
7614
  }
7616
7615
  case 'validate/form':
@@ -7664,7 +7663,7 @@ const createStepperContextInitData = props => {
7664
7663
  visible
7665
7664
  };
7666
7665
  });
7667
- const activeId = (props === null || props === void 0 ? void 0 : props.activeId) || (isPage ? TABLE_CONTEXT_ID : 0);
7666
+ const activeId = (props === null || props === void 0 ? void 0 : props.activeId) || (isPage ? categories.length + 1 : 0);
7668
7667
  return {
7669
7668
  categories: categories,
7670
7669
  activeId,
@@ -7714,7 +7713,7 @@ const JsonFormsStepperContextProvider = ({
7714
7713
  stepperDispatch({
7715
7714
  type: 'page/to/index',
7716
7715
  payload: {
7717
- id: TABLE_CONTEXT_ID
7716
+ id: stepperState.categories.length + 1
7718
7717
  }
7719
7718
  });
7720
7719
  },
@@ -7778,7 +7777,7 @@ const JsonFormsStepperContextProvider = ({
7778
7777
  }))
7779
7778
  }
7780
7779
  });
7781
- if (stepperState.activeId != TABLE_CONTEXT_ID) {
7780
+ if (stepperState.activeId !== stepperState.categories.length + 1) {
7782
7781
  context.goToPage(stepperState.maxReachedStep);
7783
7782
  context.goToPage(stepperState.activeId);
7784
7783
  }
@@ -8373,7 +8372,7 @@ const FormPagesView = props => {
8373
8372
  const handleGoToPage = index => {
8374
8373
  goToPage(index);
8375
8374
  };
8376
- if (TABLE_CONTEXT_ID === activeId) {
8375
+ if (categories.length + 1 === activeId) {
8377
8376
  const tocProps = {
8378
8377
  categories,
8379
8378
  onClick: handleGoToPage,
@@ -10224,15 +10223,15 @@ const ListItem = styled.li(_t6 || (_t6 = _$1`
10224
10223
  padding: var(--goa-space-xs) var(--goa-space-2xs) var(--goa-space-xs) var(--goa-space-xs);
10225
10224
  margin-left: 0.25rem;
10226
10225
  `), ({
10227
- selectedIndex,
10226
+ selected,
10228
10227
  index
10229
- }) => selectedIndex === index ? 'var(--goa-color-interactive-default)' : '', ({
10230
- selectedIndex,
10228
+ }) => selected === index ? 'var(--goa-color-interactive-default)' : '', ({
10229
+ selected,
10231
10230
  index
10232
- }) => selectedIndex === index ? 'var(--goa-color-greyscale-white) !important' : '', ({
10233
- selectedIndex,
10231
+ }) => selected === index ? 'var(--goa-color-greyscale-white) !important' : '', ({
10232
+ selected,
10234
10233
  index
10235
- }) => selectedIndex === index ? '600' : '');
10234
+ }) => selected === index ? '600' : '');
10236
10235
 
10237
10236
  const AddressInputs = ({
10238
10237
  address,
@@ -10489,6 +10488,21 @@ function formatPostalCodeIfNeeded(input) {
10489
10488
  const after = cleaned.slice(3);
10490
10489
  return `${before} ${after}`;
10491
10490
  }
10491
+ function handleAddressKeyDown(key, value, activeIndex, suggestions, onInputChange, onSelect) {
10492
+ if (key === 'ArrowDown') {
10493
+ const newIndex = activeIndex < suggestions.length - 1 ? activeIndex + 1 : 0;
10494
+ onInputChange(value);
10495
+ return newIndex;
10496
+ } else if (key === 'ArrowUp') {
10497
+ const newIndex = activeIndex > 0 ? activeIndex - 1 : suggestions.length - 1;
10498
+ onInputChange(value);
10499
+ return newIndex;
10500
+ } else if (key === 'Enter' && suggestions[activeIndex]) {
10501
+ onInputChange(value);
10502
+ onSelect(suggestions[activeIndex]);
10503
+ }
10504
+ return activeIndex;
10505
+ }
10492
10506
 
10493
10507
  let _ = t => t,
10494
10508
  _t,
@@ -10728,6 +10742,7 @@ const HelpContentTester = rankWith(1, uiTypeIs('HelpContent'));
10728
10742
  const HelpContent = withJsonFormsControlProps(HelpContentComponent);
10729
10743
  const HelpReviewContent = withJsonFormsControlProps(HelpContentReviewComponent);
10730
10744
 
10745
+ //type DebounceValueType = string | boolean | number | Record<string, unknown>;
10731
10746
  /**
10732
10747
  * A helper util to return a value at a certain delay. The delay is reset if the value arg changes
10733
10748
  * @param value value to be returned after a period of delay
@@ -10737,11 +10752,9 @@ const HelpReviewContent = withJsonFormsControlProps(HelpContentReviewComponent);
10737
10752
  function useDebounce(value, delay) {
10738
10753
  const [debouncedValue, setDebouncedValue] = useState(value);
10739
10754
  useEffect(() => {
10740
- // Update debounced value after delay
10741
10755
  const handler = setTimeout(() => {
10742
10756
  setDebouncedValue(value);
10743
10757
  }, delay);
10744
- // Cancel the timeout if value changes (also on delay change or unmount)
10745
10758
  return () => {
10746
10759
  clearTimeout(handler);
10747
10760
  };
@@ -10787,10 +10800,9 @@ const AddressLookUpControl = props => {
10787
10800
  const requiredFields = schema.required;
10788
10801
  const [dropdownSelected, setDropdownSelected] = useState(false);
10789
10802
  const updateFormData = updatedAddress => {
10790
- setAddress(updatedAddress);
10791
10803
  handleChange(path, updatedAddress);
10792
10804
  };
10793
- const debouncedRenderAddress = useDebounce(searchTerm, 300);
10805
+ const debouncedRenderAddress = useDebounce(searchTerm, 200);
10794
10806
  const [activeIndex, setActiveIndex] = useState(0);
10795
10807
  const dropdownRef = useRef(null);
10796
10808
  const handleInputChange = (field, value) => {
@@ -10826,29 +10838,38 @@ const AddressLookUpControl = props => {
10826
10838
  }));
10827
10839
  };
10828
10840
  useEffect(() => {
10829
- handleInputChange('addressLine1', searchTerm);
10841
+ handleInputChange('addressLine1', debouncedRenderAddress);
10830
10842
  }, [debouncedRenderAddress]); // eslint-disable-line react-hooks/exhaustive-deps
10831
10843
  useEffect(() => {
10832
10844
  const fetchSuggestions = async () => {
10833
- if (searchTerm.length > 2 && dropdownSelected === false) {
10845
+ if (debouncedRenderAddress.length > 2 && dropdownSelected === false) {
10834
10846
  setLoading(true);
10835
10847
  setOpen(true);
10836
- await fetchAddressSuggestions(formUrl, formatPostalCodeIfNeeded(searchTerm), isAlbertaAddress).then(response => {
10837
- const suggestions = filterSuggestionsWithoutAddressCount(response);
10838
- if (isAlbertaAddress) {
10839
- setSuggestions(filterAlbertaAddresses(suggestions));
10840
- } else {
10841
- setSuggestions(suggestions);
10848
+ try {
10849
+ await fetchAddressSuggestions(formUrl, formatPostalCodeIfNeeded(searchTerm), isAlbertaAddress).then(response => {
10850
+ const suggestions = filterSuggestionsWithoutAddressCount(response);
10851
+ if (isAlbertaAddress) {
10852
+ setSuggestions(filterAlbertaAddresses(suggestions));
10853
+ } else {
10854
+ setSuggestions(suggestions);
10855
+ }
10856
+ setLoading(false);
10857
+ });
10858
+ // eslint-disable-next-line
10859
+ } catch (error) {
10860
+ if (error.name !== 'AbortError') {
10861
+ console.error('Address fetch failed:', error);
10842
10862
  }
10863
+ } finally {
10843
10864
  setLoading(false);
10844
- });
10865
+ }
10845
10866
  } else {
10846
10867
  setSuggestions([]);
10847
10868
  setOpen(false);
10848
10869
  }
10849
10870
  };
10850
10871
  fetchSuggestions();
10851
- }, [searchTerm]); // eslint-disable-line react-hooks/exhaustive-deps
10872
+ }, [debouncedRenderAddress]); // eslint-disable-line react-hooks/exhaustive-deps
10852
10873
  const handleDropdownChange = value => {
10853
10874
  setSearchTerm(value);
10854
10875
  };
@@ -10883,29 +10904,6 @@ const AddressLookUpControl = props => {
10883
10904
  }
10884
10905
  }
10885
10906
  }, [activeIndex]);
10886
- const handleKeyDown = (e, value, key) => {
10887
- var _a;
10888
- if (key === 'ArrowDown') {
10889
- setActiveIndex(prevIndex => prevIndex < suggestions.length - 1 ? prevIndex + 1 : 0);
10890
- handleInputChange('addressLine1', value);
10891
- } else if (key === 'ArrowUp') {
10892
- setActiveIndex(prevIndex => prevIndex > 0 ? prevIndex - 1 : suggestions.length - 1);
10893
- handleInputChange('addressLine1', value);
10894
- } else if (key === 'Enter') {
10895
- handleInputChange('addressLine1', value);
10896
- setLoading(false);
10897
- if (activeIndex >= 0) {
10898
- (_a = document.getElementById('goaInput')) === null || _a === void 0 ? void 0 : _a.blur();
10899
- const suggestion = suggestions[activeIndex];
10900
- if (suggestion) {
10901
- setTimeout(() => {
10902
- handleSuggestionClick(suggestion);
10903
- setOpen(false);
10904
- }, 50);
10905
- }
10906
- }
10907
- }
10908
- };
10909
10907
  const readOnly = (_f = (_e = (_d = uischema === null || uischema === void 0 ? void 0 : uischema.options) === null || _d === void 0 ? void 0 : _d.componentProps) === null || _e === void 0 ? void 0 : _e.readOnly) !== null && _f !== void 0 ? _f : false;
10910
10908
  return jsxs("div", {
10911
10909
  children: [renderHelp(), jsx("h3", {
@@ -10933,7 +10931,8 @@ const AddressLookUpControl = props => {
10933
10931
  width: "100%",
10934
10932
  onKeyPress: (e, value, key) => {
10935
10933
  if (open) {
10936
- handleKeyDown(e, value, key);
10934
+ const newIndex = handleAddressKeyDown(key, value, activeIndex, suggestions, val => handleInputChange('addressLine1', val), suggestion => handleSuggestionClick(suggestion));
10935
+ setActiveIndex(newIndex);
10937
10936
  }
10938
10937
  }
10939
10938
  }), loading && jsx("div", {
@@ -10955,8 +10954,9 @@ const AddressLookUpControl = props => {
10955
10954
  onClick: () => {
10956
10955
  handleSuggestionClick(suggestion);
10957
10956
  },
10958
- selectedIndex: activeIndex,
10957
+ selected: activeIndex,
10959
10958
  index: index,
10959
+ "data-testId": `listItem-${index}`,
10960
10960
  children: `${suggestion.Text} ${suggestion.Description}`
10961
10961
  }, index))
10962
10962
  })]
@@ -11376,6 +11376,37 @@ const FullNameControlReview = props => {
11376
11376
  };
11377
11377
  const GoAInputBaseFullNameControlReview = withJsonFormsAllOfProps(FullNameControlReview);
11378
11378
 
11379
+ function useSyncAutofillFields(fields,
11380
+ // eslint-disable-next-line
11381
+ formData,
11382
+ // eslint-disable-next-line
11383
+ updateFormData,
11384
+ // eslint-disable-next-line
11385
+ handleRequiredFieldBlur) {
11386
+ useEffect(() => {
11387
+ const rAF = requestAnimationFrame(() => {
11388
+ const timeout = setTimeout(() => {
11389
+ const updated = {};
11390
+ fields.forEach(field => {
11391
+ const input = document.querySelector(`goa-input[name="${field}"]`);
11392
+ if (input && input.value && !(formData === null || formData === void 0 ? void 0 : formData[field])) {
11393
+ updated[field] = input.value;
11394
+ }
11395
+ });
11396
+ if (Object.keys(updated).length > 0) {
11397
+ const mergedData = Object.assign({}, formData, updated);
11398
+ updateFormData(mergedData);
11399
+ Object.keys(updated).forEach(name => {
11400
+ handleRequiredFieldBlur(name, mergedData);
11401
+ });
11402
+ }
11403
+ }, 50);
11404
+ return () => clearTimeout(timeout);
11405
+ });
11406
+ return () => cancelAnimationFrame(rAF);
11407
+ }, [formData]);
11408
+ }
11409
+
11379
11410
  const FullNameDobControl = props => {
11380
11411
  var _a, _b, _c, _d, _e, _f;
11381
11412
  const {
@@ -11399,33 +11430,29 @@ const FullNameDobControl = props => {
11399
11430
  const [formData, setFormData] = useState(data || defaultNameAndDob);
11400
11431
  const updateFormData = updatedData => {
11401
11432
  updatedData = Object.fromEntries(Object.entries(updatedData).filter(([_, value]) => value !== ''));
11433
+ setFormData(updatedData);
11402
11434
  handleChange(path, updatedData);
11403
11435
  };
11404
11436
  const handleInputChange = (field, value) => {
11405
11437
  const updatedData = Object.assign({}, formData, {
11406
11438
  [field]: value
11407
11439
  });
11408
- setFormData(updatedData);
11409
- updateFormData(updatedData);
11410
- };
11411
- const handleDobChange = (field, value) => {
11412
- const updatedData = Object.assign({}, formData, {
11413
- [field]: value
11414
- });
11415
- setFormData(updatedData);
11416
11440
  updateFormData(updatedData);
11417
11441
  };
11418
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11442
+ // eslint-disable-next-line
11419
11443
  const handleRequiredFieldBlur = (name, updatedData) => {
11444
+ var _a, _b, _c, _d;
11420
11445
  const err = Object.assign({}, errors);
11421
- if ((!(data === null || data === void 0 ? void 0 : data[name]) || (data === null || data === void 0 ? void 0 : data[name]) === '') && requiredFields.includes(name) && (!updatedData)) {
11422
- const modifiedName = name === 'firstName' ? 'First name' : name === 'lastName' ? 'Last name' : name === 'dateOfBirth' ? 'Date of birth' : '';
11446
+ const liveInputValue = (_d = (_b = (_a = updatedData === null || updatedData === void 0 ? void 0 : updatedData[name]) !== null && _a !== void 0 ? _a : data === null || data === void 0 ? void 0 : data[name]) !== null && _b !== void 0 ? _b : (_c = document.querySelector(`goa-input[name="${name}"]`)) === null || _c === void 0 ? void 0 : _c.value) !== null && _d !== void 0 ? _d : '';
11447
+ if (requiredFields.includes(name) && liveInputValue.trim() === '') {
11448
+ const modifiedName = name === 'firstName' ? 'First name' : name === 'lastName' ? 'Last name' : name === 'dateOfBirth' ? 'Date of birth' : name;
11423
11449
  err[name] = `${modifiedName} is required`;
11424
11450
  } else {
11425
- err[name] = '';
11451
+ delete err[name];
11426
11452
  }
11427
11453
  setErrors(err);
11428
11454
  };
11455
+ useSyncAutofillFields(['firstName', 'middleName', 'lastName', 'dateOfBirth'], formData, updateFormData, handleRequiredFieldBlur);
11429
11456
  return jsxs(Fragment, {
11430
11457
  children: [jsxs(GoAGrid, {
11431
11458
  minChildWidth: "0ch",
@@ -11435,13 +11462,14 @@ const FullNameDobControl = props => {
11435
11462
  label: "First name",
11436
11463
  requirement: ((_a = schema === null || schema === void 0 ? void 0 : schema.required) === null || _a === void 0 ? void 0 : _a.includes('firstName')) ? 'required' : undefined,
11437
11464
  error: (_b = errors === null || errors === void 0 ? void 0 : errors['firstName']) !== null && _b !== void 0 ? _b : '',
11465
+ testId: "form-item-first-name",
11438
11466
  children: jsx(GoAInput, {
11439
11467
  disabled: !enabled,
11440
11468
  type: "text",
11441
11469
  name: "firstName",
11442
11470
  testId: "name-form-first-name",
11443
11471
  ariaLabel: 'name-form-first-name',
11444
- value: formData.firstName || '',
11472
+ value: formData.firstName,
11445
11473
  onChange: (name, value) => {
11446
11474
  handleInputChange(name, value);
11447
11475
  },
@@ -11475,9 +11503,10 @@ const FullNameDobControl = props => {
11475
11503
  testId: "name-form-last-name",
11476
11504
  ariaLabel: 'name-form-last-name',
11477
11505
  value: formData.lastName || '',
11478
- onChange: (name, value) => handleInputChange(name, value),
11506
+ onChange: (name, value) => {
11507
+ handleInputChange(name, value);
11508
+ },
11479
11509
  onBlur: name => {
11480
- /* istanbul ignore next */
11481
11510
  handleRequiredFieldBlur(name);
11482
11511
  },
11483
11512
  width: "100%"
@@ -11501,7 +11530,7 @@ const FullNameDobControl = props => {
11501
11530
  ariaLabel: "dob-form-dateOfBirth",
11502
11531
  placeholder: "YYYY-MM-DD",
11503
11532
  value: formData === null || formData === void 0 ? void 0 : formData.dateOfBirth,
11504
- onChange: (name, value) => handleDobChange(name, value),
11533
+ onChange: (name, value) => handleInputChange(name, value),
11505
11534
  onBlur: name => {
11506
11535
  /* istanbul ignore next */
11507
11536
  handleRequiredFieldBlur(name);
@@ -11907,8 +11936,20 @@ const createDefaultAjv = (...schemas) => {
11907
11936
  ajv.addSchema(schemas);
11908
11937
  addErrors(ajv);
11909
11938
  addFormats(ajv);
11910
- ajv.addFormat('file-urn', /^urn:ads:platform:file-service:v[0-9]:\/files\/[a-zA-Z0-9.-]*$/);
11911
11939
  ajv.addFormat('time', /^([01]\d|2[0-3]):[0-5]\d(:[0-5]\d)?$/);
11940
+ ajv.addFormat('file-urn', {
11941
+ type: 'string',
11942
+ validate: input => {
11943
+ const fileUrnRegExp = new RegExp('^urn:ads:platform:file-service:v[0-9]:/files/[a-zA-Z0-9.-]*$');
11944
+ const fileUrns = input.split(';');
11945
+ for (const urn of fileUrns) {
11946
+ if (!fileUrnRegExp.test(urn)) {
11947
+ return false;
11948
+ }
11949
+ }
11950
+ return true;
11951
+ }
11952
+ });
11912
11953
  return ajv;
11913
11954
  };
11914
11955
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abgov/jsonforms-components",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Government of Alberta - React renderers for JSON Forms based on the design system.",
6
6
  "repository": "https://github.com/GovAlta/adsp-monorepo",
@@ -1,5 +1,5 @@
1
1
  interface ListItemProps {
2
- selectedIndex: number;
2
+ selected: number;
3
3
  index: number;
4
4
  }
5
5
  export declare const SearchBox: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
@@ -10,3 +10,4 @@ export declare const handlePostalCodeValidation: (validatePc: boolean, message:
10
10
  export declare const formatPostalCode: (value: string) => string;
11
11
  export declare function detectPostalCodeType(input: string): 'full' | 'partial' | 'none';
12
12
  export declare function formatPostalCodeIfNeeded(input: string): string;
13
+ export declare function handleAddressKeyDown(key: string, value: string, activeIndex: number, suggestions: Suggestion[], onInputChange: (value: string) => void, onSelect: (suggestion: Suggestion) => void): number;
@@ -27,4 +27,3 @@ export interface StepperContextDataType {
27
27
  maxReachedStep: number;
28
28
  }
29
29
  export type CategorizationElement = Category | Categorization;
30
- export declare const TABLE_CONTEXT_ID = 100001;
@@ -1,9 +1,7 @@
1
- type DebounceValueType = string | boolean | number | Record<string, unknown>;
2
1
  /**
3
2
  * A helper util to return a value at a certain delay. The delay is reset if the value arg changes
4
3
  * @param value value to be returned after a period of delay
5
4
  * @param delay time in ms to apply the delay
6
5
  * @returns value after the delay timer
7
6
  */
8
- export declare function useDebounce(value: DebounceValueType, delay: number): DebounceValueType;
9
- export {};
7
+ export declare function useDebounce<T>(value: T, delay: number): T;
@@ -0,0 +1 @@
1
+ export declare function useSyncAutofillFields(fields: string[], formData: any, updateFormData: (data: any) => void, handleRequiredFieldBlur: (name: string, updatedData?: any) => void): void;