@abgov/jsonforms-components 2.75.1 → 2.75.3

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
@@ -3902,6 +3902,38 @@ $$x({ target: 'Iterator', proto: true, real: true, forced: IS_PURE$2 }, {
3902
3902
  map: map
3903
3903
  });
3904
3904
 
3905
+ const sinTitle = 'Social insurance number';
3906
+ const invalidSin = 'Social insurance number is invalid';
3907
+ const DEFAULT_MAX_ITEMS = 50;
3908
+ const REQUIRED_PROPERTY_ERROR = 'is a required property';
3909
+ const ADDRESS_LOOKUP_LABELS = {
3910
+ addressLine1: 'Address line 1',
3911
+ addressLine2: 'Address line 2',
3912
+ municipality: 'City',
3913
+ postalCode: 'Postal code',
3914
+ subdivisionCode: 'Province',
3915
+ country: 'Country'
3916
+ };
3917
+ const VALIDATION_KEYWORDS = {
3918
+ REQUIRED: 'required',
3919
+ MIN_LENGTH: 'minLength',
3920
+ MAX_LENGTH: 'maxLength',
3921
+ FORMAT: 'format',
3922
+ MINIMUM: 'minimum',
3923
+ MAXIMUM: 'maximum',
3924
+ TYPE: 'type',
3925
+ ERROR_MESSAGE: 'errorMessage'
3926
+ };
3927
+ var StepStatus;
3928
+ (function (StepStatus) {
3929
+ StepStatus["NOT_STARTED"] = "NotStarted";
3930
+ StepStatus["IN_PROGRESS"] = "InProgress";
3931
+ StepStatus["COMPLETED"] = "Completed";
3932
+ })(StepStatus || (StepStatus = {}));
3933
+ const getAddressLookupFieldLabel = fieldName => {
3934
+ return ADDRESS_LOOKUP_LABELS[fieldName] || fieldName;
3935
+ };
3936
+
3905
3937
  var classof$5 = classofRaw$2;
3906
3938
 
3907
3939
  // `IsArray` abstract operation
@@ -4558,6 +4590,222 @@ const isRequiredBySchema = (rootSchema, rootData, path, options = {}) => {
4558
4590
  return reqSet.has(dotPath);
4559
4591
  };
4560
4592
 
4593
+ // Default formats keyed by type; the mask can be overridden per field via UI schema options.
4594
+ const DEFAULT_PATTERNS = {
4595
+ phone: {
4596
+ mask: '(###) ###-####',
4597
+ pattern: /^\(?\d{3}\)?[- ]?\d{3}[- ]?\d{4}$/,
4598
+ error: 'Must be a valid 10-digit phone number in format (000) 000-0000',
4599
+ allowedKeys: /[0-9]/
4600
+ },
4601
+ sin: {
4602
+ mask: '### ### ###',
4603
+ pattern: /^\d{3} \d{3} \d{3}$/,
4604
+ error: 'Must be a valid social insurance number in format 000 000 000',
4605
+ allowedKeys: /[0-9]/
4606
+ },
4607
+ postalCode: {
4608
+ mask: '### ###',
4609
+ pattern: /^[A-Za-z]\d[A-Za-z] \d[A-Za-z]\d$/,
4610
+ error: 'Must be in A0A 0A0 capital letters and numbers format',
4611
+ allowedKeys: /[A-Za-z0-9]/
4612
+ },
4613
+ driverId: {
4614
+ // Alberta driver's licence: six digits, a dash, then three digits.
4615
+ mask: '######-###',
4616
+ pattern: /^\d{6}-\d{3}$/,
4617
+ error: "Must be a valid driver's licence number in format 000000-000",
4618
+ allowedKeys: /[0-9]/
4619
+ },
4620
+ mvid: {
4621
+ // Motor vehicle ID: four digits, a dash, then five digits.
4622
+ mask: '####-#####',
4623
+ pattern: /^\d{4}-\d{5}$/,
4624
+ error: 'Must be a valid motor vehicle ID in format 0000-00000',
4625
+ allowedKeys: /[0-9]/
4626
+ }
4627
+ };
4628
+ // Resolve the mask format for a field: schema.format must match a DEFAULT_PATTERNS key.
4629
+ const resolveFormatConfig = schema => {
4630
+ if (!schema) {
4631
+ return undefined;
4632
+ }
4633
+ if (schema.format && schema.format in DEFAULT_PATTERNS) {
4634
+ return DEFAULT_PATTERNS[schema.format];
4635
+ }
4636
+ return schema.title === sinTitle ? DEFAULT_PATTERNS.sin : undefined;
4637
+ };
4638
+ const getSchemaErrorMessage = errorMessage => {
4639
+ if (typeof errorMessage === 'string' && errorMessage.trim() !== '') {
4640
+ return errorMessage;
4641
+ }
4642
+ if (errorMessage && typeof errorMessage === 'object') {
4643
+ const configured = errorMessage.pattern || errorMessage.format;
4644
+ if (typeof configured === 'string' && configured.trim() !== '') {
4645
+ return configured;
4646
+ }
4647
+ }
4648
+ return undefined;
4649
+ };
4650
+ // Prefer a schema or format-config error; callers fall back to AJV when this is empty.
4651
+ const getConfiguredFormatError = schema => {
4652
+ var _resolveFormatConfig;
4653
+ return getSchemaErrorMessage(schema == null ? void 0 : schema.errorMessage) || ((_resolveFormatConfig = resolveFormatConfig(schema)) == null ? void 0 : _resolveFormatConfig.error);
4654
+ };
4655
+ // Mask placeholder characters; each consumes one input character. Any other mask character is treated as a literal.
4656
+ const MASK_PLACEHOLDERS = new Set(['#', '*', '_']);
4657
+ const isPlaceholder = char => MASK_PLACEHOLDERS.has(char);
4658
+ // Navigation/editing keys that keystroke filtering must always let through.
4659
+ const MASK_CONTROL_KEYS = new Set(['Backspace', 'Delete', 'Tab', 'Enter', 'Escape', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End']);
4660
+ // A single-character key should be blocked when the format restricts input and the key isn't allowed.
4661
+ const shouldBlockKey = (key, allowedKeys) => !!allowedKeys && key.length === 1 && !MASK_CONTROL_KEYS.has(key) && !allowedKeys.test(key);
4662
+ // Removes characters the format doesn't allow (e.g. letters from a digit-only field), for paste/programmatic input.
4663
+ const filterAllowedKeys = (value, allowedKeys) => allowedKeys ? [...(value != null ? value : '')].filter(char => allowedKeys.test(char)).join('') : value != null ? value : '';
4664
+ // A mask slot holds one content character (letter or digit); everything else in the value is a separator.
4665
+ const NON_CONTENT = /[^a-zA-Z0-9]/g;
4666
+ const IS_CONTENT = /[a-zA-Z0-9]/;
4667
+ // Number of fill slots in a mask (its placeholder characters).
4668
+ const maskDigitCount = mask => [...mask].filter(isPlaceholder).length;
4669
+ // Formats a value into a placeholder mask (e.g. '(###) ###-####'), filling only the content entered so far.
4670
+ // Placeholder characters consume one content character each; every other mask character is a literal.
4671
+ const formatWithPattern = (value, mask) => {
4672
+ const maxContent = maskDigitCount(mask);
4673
+ const content = (value != null ? value : '').replace(NON_CONTENT, '').slice(0, maxContent);
4674
+ let index = 0;
4675
+ let formatted = '';
4676
+ for (const maskChar of mask) {
4677
+ if (index >= content.length) {
4678
+ break;
4679
+ }
4680
+ formatted += isPlaceholder(maskChar) ? content[index++] : maskChar;
4681
+ }
4682
+ return formatted;
4683
+ };
4684
+ // Fills the mask with the value's content, leaving the placeholder character for positions not yet entered.
4685
+ const toMaskTemplate = (value, mask) => {
4686
+ const maxContent = maskDigitCount(mask);
4687
+ const content = (value != null ? value : '').replace(NON_CONTENT, '').slice(0, maxContent);
4688
+ let index = 0;
4689
+ let display = '';
4690
+ for (const maskChar of mask) {
4691
+ display += isPlaceholder(maskChar) ? index < content.length ? content[index++] : maskChar : maskChar;
4692
+ }
4693
+ return display;
4694
+ };
4695
+ // Caret position just after the Nth content character within a rendered mask, so in-place edits keep the cursor in place.
4696
+ const caretAfterDigits = (display, digitCount) => {
4697
+ if (digitCount <= 0) {
4698
+ return 0;
4699
+ }
4700
+ let seen = 0;
4701
+ for (let i = 0; i < display.length; i++) {
4702
+ if (IS_CONTENT.test(display[i])) {
4703
+ seen += 1;
4704
+ if (seen === digitCount) {
4705
+ return i + 1;
4706
+ }
4707
+ }
4708
+ }
4709
+ return display.length;
4710
+ };
4711
+ // Computes an in-place masked edit from the raw input and caret index: what to show, what to store, where to put the caret.
4712
+ const computeMaskEdit = (rawValue, caretIndex, mask) => {
4713
+ const digitsBeforeCaret = rawValue.slice(0, caretIndex).replace(NON_CONTENT, '').length;
4714
+ const display = toMaskTemplate(rawValue, mask);
4715
+ const stored = formatWithPattern(rawValue, mask);
4716
+ const caret = caretAfterDigits(display, Math.min(digitsBeforeCaret, maskDigitCount(mask)));
4717
+ return {
4718
+ display,
4719
+ stored,
4720
+ caret
4721
+ };
4722
+ };
4723
+ // True once the value has as many content characters as the mask has fill slots.
4724
+ const isMaskFilled = (value, mask) => value.replace(NON_CONTENT, '').length >= maskDigitCount(mask);
4725
+ // A human placeholder for a mask, e.g. '(###) ###-####' -> '(000) 000-0000'.
4726
+ const maskPlaceholder = mask => [...mask].map(char => isPlaceholder(char) ? '0' : char).join('');
4727
+ // GoA retargets composed events to the host, so prefer the inner native input from the composed path.
4728
+ const getMaskInputTarget = detail => {
4729
+ var _target$shadowRoot$qu, _target$shadowRoot;
4730
+ const event = detail.event;
4731
+ if (!event) {
4732
+ return undefined;
4733
+ }
4734
+ const path = typeof event.composedPath === 'function' ? event.composedPath() : [];
4735
+ const fromPath = path.find(node => node instanceof HTMLInputElement);
4736
+ if (fromPath) {
4737
+ return fromPath;
4738
+ }
4739
+ const target = event.target;
4740
+ if (target instanceof HTMLInputElement) {
4741
+ return target;
4742
+ }
4743
+ return (_target$shadowRoot$qu = target == null || (_target$shadowRoot = target.shadowRoot) == null ? void 0 : _target$shadowRoot.querySelector('input')) != null ? _target$shadowRoot$qu : undefined;
4744
+ };
4745
+ const applyMaskValue = (target, edit) => {
4746
+ target.value = edit.display;
4747
+ target.setSelectionRange == null || target.setSelectionRange(edit.caret, edit.caret);
4748
+ };
4749
+ // Applies an in-place edit to the inner input and re-applies after the web component paints.
4750
+ const applyInPlaceEdit = (target, edit) => {
4751
+ if (!target) {
4752
+ return;
4753
+ }
4754
+ applyMaskValue(target, edit);
4755
+ requestAnimationFrame(() => applyMaskValue(target, edit));
4756
+ };
4757
+ // When the template is already filled, extra content characters must be rejected rather than shifting the value.
4758
+ const overflowMaskEdit = (previousDisplay, rawValue, caretIndex, mask) => {
4759
+ if (!isMaskFilled(previousDisplay, mask)) {
4760
+ return undefined;
4761
+ }
4762
+ if ((rawValue != null ? rawValue : '').replace(NON_CONTENT, '').length <= maskDigitCount(mask)) {
4763
+ return undefined;
4764
+ }
4765
+ return {
4766
+ display: previousDisplay,
4767
+ stored: formatWithPattern(previousDisplay, mask),
4768
+ caret: Math.min(Math.max(caretIndex, 0), previousDisplay.length)
4769
+ };
4770
+ };
4771
+ /**
4772
+ * Applies a mask template to a value, e.g. mask "(###) ###-####" turns "7801234567" into "(780) 123-4567".
4773
+ * '#' consumes one content character from the input; every other character is a literal inserted into the output.
4774
+ * The mask's literal characters are stripped from the input first so an already formatted value re-formats cleanly.
4775
+ * Returns the original value unchanged when the mask is empty or no content characters remain.
4776
+ * @param value - The raw input value
4777
+ * @param mask - The mask template string
4778
+ * @returns The masked value
4779
+ */
4780
+ const applyFormatPattern = (value, mask) => {
4781
+ if (!mask || !value) {
4782
+ return value;
4783
+ }
4784
+ const literals = new Set(mask.split('').filter(char => !isPlaceholder(char)));
4785
+ const content = value.split('').filter(char => !literals.has(char));
4786
+ if (content.length === 0) {
4787
+ return value;
4788
+ }
4789
+ let contentIndex = 0;
4790
+ let result = '';
4791
+ for (const maskChar of mask) {
4792
+ if (contentIndex >= content.length) {
4793
+ break;
4794
+ }
4795
+ if (isPlaceholder(maskChar)) {
4796
+ result += content[contentIndex];
4797
+ contentIndex += 1;
4798
+ } else {
4799
+ result += maskChar;
4800
+ }
4801
+ }
4802
+ // Append any remaining content characters that did not fit the mask.
4803
+ if (contentIndex < content.length) {
4804
+ result += content.slice(contentIndex).join('');
4805
+ }
4806
+ return result;
4807
+ };
4808
+
4561
4809
  /**
4562
4810
  * Sets the first word to be capitalized so that it is sentence cased.
4563
4811
  * @param words
@@ -4755,7 +5003,10 @@ const checkFieldValidity = (props, rootData) => {
4755
5003
  if (isRegisterBackedEnum) {
4756
5004
  return '';
4757
5005
  }
4758
- return ajvErrors;
5006
+ if (!ajvErrors || ajvErrors === invalidSin) {
5007
+ return ajvErrors;
5008
+ }
5009
+ return getConfiguredFormatError(schema) || ajvErrors;
4759
5010
  };
4760
5011
  /**
4761
5012
  * Check if the date is a valid date/time
@@ -4821,195 +5072,6 @@ const UTCToFullLocalTime = (fullTime, useUTC = false) => {
4821
5072
  }).replace('p.m.', 'PM').replace('a.m.', 'AM');
4822
5073
  };
4823
5074
 
4824
- // Default formats keyed by type; the mask can be overridden per field via UI schema options.
4825
- const DEFAULT_PATTERNS = {
4826
- phone: {
4827
- mask: '(###) ###-####',
4828
- pattern: /^\(?\d{3}\)?[- ]?\d{3}[- ]?\d{4}$/,
4829
- error: 'Must be a valid 10-digit phone number in format (000) 000-0000',
4830
- allowedKeys: /[0-9]/
4831
- },
4832
- sin: {
4833
- mask: '### ### ###',
4834
- pattern: /^\d{3} \d{3} \d{3}$/,
4835
- error: 'Must be three groups of three digits.',
4836
- allowedKeys: /[0-9]/
4837
- },
4838
- postalCode: {
4839
- mask: '### ###',
4840
- pattern: /^[A-Za-z]\d[A-Za-z] \d[A-Za-z]\d$/,
4841
- error: 'Must be in A0A 0A0 capital letters and numbers format',
4842
- allowedKeys: /[A-Za-z0-9]/
4843
- },
4844
- driverId: {
4845
- // Alberta driver's licence: six digits, a dash, then three digits.
4846
- mask: '######-###',
4847
- pattern: /^\d{6}-\d{3}$/,
4848
- error: "Must be a valid driver's licence number in format 000000-000",
4849
- allowedKeys: /[0-9]/
4850
- },
4851
- mvid: {
4852
- // Motor vehicle ID: four digits, a dash, then five digits.
4853
- mask: '####-#####',
4854
- pattern: /^\d{4}-\d{5}$/,
4855
- error: 'Must be a valid motor vehicle ID in format 0000-00000',
4856
- allowedKeys: /[0-9]/
4857
- }
4858
- };
4859
- // Mask placeholder characters; each consumes one input character. Any other mask character is treated as a literal.
4860
- const MASK_PLACEHOLDERS = new Set(['#', '*', '_']);
4861
- const isPlaceholder = char => MASK_PLACEHOLDERS.has(char);
4862
- // Navigation/editing keys that keystroke filtering must always let through.
4863
- const MASK_CONTROL_KEYS = new Set(['Backspace', 'Delete', 'Tab', 'Enter', 'Escape', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End']);
4864
- // A single-character key should be blocked when the format restricts input and the key isn't allowed.
4865
- const shouldBlockKey = (key, allowedKeys) => !!allowedKeys && key.length === 1 && !MASK_CONTROL_KEYS.has(key) && !allowedKeys.test(key);
4866
- // Removes characters the format doesn't allow (e.g. letters from a digit-only field), for paste/programmatic input.
4867
- const filterAllowedKeys = (value, allowedKeys) => allowedKeys ? [...(value != null ? value : '')].filter(char => allowedKeys.test(char)).join('') : value != null ? value : '';
4868
- // A mask slot holds one content character (letter or digit); everything else in the value is a separator.
4869
- const NON_CONTENT = /[^a-zA-Z0-9]/g;
4870
- const IS_CONTENT = /[a-zA-Z0-9]/;
4871
- // Number of fill slots in a mask (its placeholder characters).
4872
- const maskDigitCount = mask => [...mask].filter(isPlaceholder).length;
4873
- // Formats a value into a placeholder mask (e.g. '(###) ###-####'), filling only the content entered so far.
4874
- // Placeholder characters consume one content character each; every other mask character is a literal.
4875
- const formatWithPattern = (value, mask) => {
4876
- const maxContent = maskDigitCount(mask);
4877
- const content = (value != null ? value : '').replace(NON_CONTENT, '').slice(0, maxContent);
4878
- let index = 0;
4879
- let formatted = '';
4880
- for (const maskChar of mask) {
4881
- if (index >= content.length) {
4882
- break;
4883
- }
4884
- formatted += isPlaceholder(maskChar) ? content[index++] : maskChar;
4885
- }
4886
- return formatted;
4887
- };
4888
- // Fills the mask with the value's content, leaving the placeholder character for positions not yet entered.
4889
- const toMaskTemplate = (value, mask) => {
4890
- const maxContent = maskDigitCount(mask);
4891
- const content = (value != null ? value : '').replace(NON_CONTENT, '').slice(0, maxContent);
4892
- let index = 0;
4893
- let display = '';
4894
- for (const maskChar of mask) {
4895
- display += isPlaceholder(maskChar) ? index < content.length ? content[index++] : maskChar : maskChar;
4896
- }
4897
- return display;
4898
- };
4899
- // Caret position just after the Nth content character within a rendered mask, so in-place edits keep the cursor in place.
4900
- const caretAfterDigits = (display, digitCount) => {
4901
- if (digitCount <= 0) {
4902
- return 0;
4903
- }
4904
- let seen = 0;
4905
- for (let i = 0; i < display.length; i++) {
4906
- if (IS_CONTENT.test(display[i])) {
4907
- seen += 1;
4908
- if (seen === digitCount) {
4909
- return i + 1;
4910
- }
4911
- }
4912
- }
4913
- return display.length;
4914
- };
4915
- // Computes an in-place masked edit from the raw input and caret index: what to show, what to store, where to put the caret.
4916
- const computeMaskEdit = (rawValue, caretIndex, mask) => {
4917
- const digitsBeforeCaret = rawValue.slice(0, caretIndex).replace(NON_CONTENT, '').length;
4918
- const display = toMaskTemplate(rawValue, mask);
4919
- const stored = formatWithPattern(rawValue, mask);
4920
- const caret = caretAfterDigits(display, Math.min(digitsBeforeCaret, maskDigitCount(mask)));
4921
- return {
4922
- display,
4923
- stored,
4924
- caret
4925
- };
4926
- };
4927
- // True once the value has as many content characters as the mask has fill slots.
4928
- const isMaskFilled = (value, mask) => value.replace(NON_CONTENT, '').length >= maskDigitCount(mask);
4929
- // A human placeholder for a mask, e.g. '(###) ###-####' -> '(000) 000-0000'.
4930
- const maskPlaceholder = mask => [...mask].map(char => isPlaceholder(char) ? '0' : char).join('');
4931
- // GoA retargets composed events to the host, so prefer the inner native input from the composed path.
4932
- const getMaskInputTarget = detail => {
4933
- var _target$shadowRoot$qu, _target$shadowRoot;
4934
- const event = detail.event;
4935
- if (!event) {
4936
- return undefined;
4937
- }
4938
- const path = typeof event.composedPath === 'function' ? event.composedPath() : [];
4939
- const fromPath = path.find(node => node instanceof HTMLInputElement);
4940
- if (fromPath) {
4941
- return fromPath;
4942
- }
4943
- const target = event.target;
4944
- if (target instanceof HTMLInputElement) {
4945
- return target;
4946
- }
4947
- return (_target$shadowRoot$qu = target == null || (_target$shadowRoot = target.shadowRoot) == null ? void 0 : _target$shadowRoot.querySelector('input')) != null ? _target$shadowRoot$qu : undefined;
4948
- };
4949
- const applyMaskValue = (target, edit) => {
4950
- target.value = edit.display;
4951
- target.setSelectionRange == null || target.setSelectionRange(edit.caret, edit.caret);
4952
- };
4953
- // Applies an in-place edit to the inner input and re-applies after the web component paints.
4954
- const applyInPlaceEdit = (target, edit) => {
4955
- if (!target) {
4956
- return;
4957
- }
4958
- applyMaskValue(target, edit);
4959
- requestAnimationFrame(() => applyMaskValue(target, edit));
4960
- };
4961
- // When the template is already filled, extra content characters must be rejected rather than shifting the value.
4962
- const overflowMaskEdit = (previousDisplay, rawValue, caretIndex, mask) => {
4963
- if (!isMaskFilled(previousDisplay, mask)) {
4964
- return undefined;
4965
- }
4966
- if ((rawValue != null ? rawValue : '').replace(NON_CONTENT, '').length <= maskDigitCount(mask)) {
4967
- return undefined;
4968
- }
4969
- return {
4970
- display: previousDisplay,
4971
- stored: formatWithPattern(previousDisplay, mask),
4972
- caret: Math.min(Math.max(caretIndex, 0), previousDisplay.length)
4973
- };
4974
- };
4975
- /**
4976
- * Applies a mask template to a value, e.g. mask "(###) ###-####" turns "7801234567" into "(780) 123-4567".
4977
- * '#' consumes one content character from the input; every other character is a literal inserted into the output.
4978
- * The mask's literal characters are stripped from the input first so an already formatted value re-formats cleanly.
4979
- * Returns the original value unchanged when the mask is empty or no content characters remain.
4980
- * @param value - The raw input value
4981
- * @param mask - The mask template string
4982
- * @returns The masked value
4983
- */
4984
- const applyFormatPattern = (value, mask) => {
4985
- if (!mask || !value) {
4986
- return value;
4987
- }
4988
- const literals = new Set(mask.split('').filter(char => !isPlaceholder(char)));
4989
- const content = value.split('').filter(char => !literals.has(char));
4990
- if (content.length === 0) {
4991
- return value;
4992
- }
4993
- let contentIndex = 0;
4994
- let result = '';
4995
- for (const maskChar of mask) {
4996
- if (contentIndex >= content.length) {
4997
- break;
4998
- }
4999
- if (isPlaceholder(maskChar)) {
5000
- result += content[contentIndex];
5001
- contentIndex += 1;
5002
- } else {
5003
- result += maskChar;
5004
- }
5005
- }
5006
- // Append any remaining content characters that did not fit the mask.
5007
- if (contentIndex < content.length) {
5008
- result += content.slice(contentIndex).join('');
5009
- }
5010
- return result;
5011
- };
5012
-
5013
5075
  /**
5014
5076
  * Checks input controls data value to determine is required and has any data.
5015
5077
  * @param props - The JsonForm control props
@@ -7602,38 +7664,6 @@ function buildConditionalDeps(rootSchema) {
7602
7664
  return out;
7603
7665
  }
7604
7666
 
7605
- const sinTitle = 'Social insurance number';
7606
- const invalidSin = 'Social insurance number is invalid';
7607
- const DEFAULT_MAX_ITEMS = 50;
7608
- const REQUIRED_PROPERTY_ERROR = 'is a required property';
7609
- const ADDRESS_LOOKUP_LABELS = {
7610
- addressLine1: 'Address line 1',
7611
- addressLine2: 'Address line 2',
7612
- municipality: 'City',
7613
- postalCode: 'Postal code',
7614
- subdivisionCode: 'Province',
7615
- country: 'Country'
7616
- };
7617
- const VALIDATION_KEYWORDS = {
7618
- REQUIRED: 'required',
7619
- MIN_LENGTH: 'minLength',
7620
- MAX_LENGTH: 'maxLength',
7621
- FORMAT: 'format',
7622
- MINIMUM: 'minimum',
7623
- MAXIMUM: 'maximum',
7624
- TYPE: 'type',
7625
- ERROR_MESSAGE: 'errorMessage'
7626
- };
7627
- var StepStatus;
7628
- (function (StepStatus) {
7629
- StepStatus["NOT_STARTED"] = "NotStarted";
7630
- StepStatus["IN_PROGRESS"] = "InProgress";
7631
- StepStatus["COMPLETED"] = "Completed";
7632
- })(StepStatus || (StepStatus = {}));
7633
- const getAddressLookupFieldLabel = fieldName => {
7634
- return ADDRESS_LOOKUP_LABELS[fieldName] || fieldName;
7635
- };
7636
-
7637
7667
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
7638
7668
  function hasMeaningfulValue(val) {
7639
7669
  if (val === undefined || val === null) return false;
@@ -10036,13 +10066,6 @@ const resetInputValue = (detail, value) => {
10036
10066
  target.value = value;
10037
10067
  }
10038
10068
  };
10039
- // Resolve the mask format for a field: schema.format must match a DEFAULT_PATTERNS key.
10040
- const resolveFormatConfig = schema => {
10041
- if (schema.format && schema.format in DEFAULT_PATTERNS) {
10042
- return DEFAULT_PATTERNS[schema.format];
10043
- }
10044
- return schema.title === sinTitle ? DEFAULT_PATTERNS.sin : undefined;
10045
- };
10046
10069
  const GoAInputText = props => {
10047
10070
  return jsxs(JsonFormRegisterProvider, {
10048
10071
  defaultRegisters: undefined,
@@ -13867,10 +13890,17 @@ const FullNameDobReviewControl = props => {
13867
13890
  const GoAInputBaseFullNameDobControlReview = withJsonFormsAllOfProps(FullNameDobReviewControl);
13868
13891
 
13869
13892
  // Encapsulates masked-input state for a GoA input: display value, mount reflection, and change/keypress handling.
13893
+ const resetMaskedValue = (detail, nextValue) => {
13894
+ const target = getMaskInputTarget(detail);
13895
+ if (target) {
13896
+ target.value = nextValue;
13897
+ }
13898
+ };
13870
13899
  const useMaskedInput = ({
13871
13900
  mask,
13872
13901
  inPlace,
13873
13902
  data,
13903
+ allowedKeys,
13874
13904
  onCommit
13875
13905
  }) => {
13876
13906
  const format = value => inPlace ? toMaskTemplate(value, mask) : formatWithPattern(value, mask);
@@ -13886,20 +13916,24 @@ const useMaskedInput = ({
13886
13916
  const handleChange = detail => {
13887
13917
  var _target$selectionStar;
13888
13918
  const rawValue = detail.value;
13919
+ const cleaned = allowedKeys ? filterAllowedKeys(rawValue, allowedKeys) : rawValue;
13889
13920
  if (!inPlace) {
13890
- const stored = formatWithPattern(rawValue, mask);
13921
+ const stored = cleaned === '' ? '' : formatWithPattern(cleaned, mask);
13922
+ if (allowedKeys && cleaned !== rawValue) {
13923
+ resetMaskedValue(detail, stored);
13924
+ }
13891
13925
  setValue(stored);
13892
13926
  onCommit(stored);
13893
13927
  return;
13894
13928
  }
13895
13929
  const target = getMaskInputTarget(detail);
13896
- const caretIndex = (_target$selectionStar = target == null ? void 0 : target.selectionStart) != null ? _target$selectionStar : rawValue.length;
13897
- const overflow = overflowMaskEdit(value, rawValue, caretIndex, mask);
13930
+ const caretIndex = (_target$selectionStar = target == null ? void 0 : target.selectionStart) != null ? _target$selectionStar : cleaned.length;
13931
+ const overflow = overflowMaskEdit(value, cleaned, caretIndex, mask);
13898
13932
  if (overflow) {
13899
13933
  applyInPlaceEdit(target, overflow);
13900
13934
  return;
13901
13935
  }
13902
- const edit = computeMaskEdit(rawValue, caretIndex, mask);
13936
+ const edit = computeMaskEdit(cleaned, caretIndex, mask);
13903
13937
  applyInPlaceEdit(target, edit);
13904
13938
  setValue(edit.display);
13905
13939
  onCommit(edit.stored);
@@ -13907,10 +13941,15 @@ const useMaskedInput = ({
13907
13941
  // In-place has no maxLength (the template is already full length) and GoA keyPress is keyup, so restore on overflow.
13908
13942
  const handleKeyPress = detail => {
13909
13943
  var _detail$event, _target$value, _target$selectionStar2;
13910
- if (!inPlace || !/^[A-Za-z0-9]$/.test(detail.key) || !isMaskFilled(value, mask)) {
13944
+ const blockDisallowed = shouldBlockKey(detail.key, allowedKeys);
13945
+ const blockOverflow = inPlace && /^[A-Za-z0-9]$/.test(detail.key) && isMaskFilled(value, mask);
13946
+ if (!blockDisallowed && !blockOverflow) {
13911
13947
  return;
13912
13948
  }
13913
13949
  (_detail$event = detail.event) == null || _detail$event.preventDefault();
13950
+ if (!blockOverflow) {
13951
+ return;
13952
+ }
13914
13953
  const target = getMaskInputTarget(detail);
13915
13954
  const rawValue = (_target$value = target == null ? void 0 : target.value) != null ? _target$value : value;
13916
13955
  const overflow = overflowMaskEdit(value, rawValue, (_target$selectionStar2 = target == null ? void 0 : target.selectionStart) != null ? _target$selectionStar2 : value.length, mask);
@@ -13950,6 +13989,7 @@ const PhoneNumberControl = props => {
13950
13989
  mask: phoneMask,
13951
13990
  inPlace,
13952
13991
  data,
13992
+ allowedKeys: DEFAULT_PATTERNS.phone.allowedKeys,
13953
13993
  onCommit: stored => {
13954
13994
  setError(stored && !DEFAULT_PATTERNS.phone.pattern.test(stored) ? DEFAULT_PATTERNS.phone.error : '');
13955
13995
  handleChange(path, stored);
@@ -13972,7 +14012,7 @@ const PhoneNumberControl = props => {
13972
14012
  maxLength: inPlace ? undefined : phoneMask.length,
13973
14013
  placeholder: inPlace ? undefined : maskPlaceholder(phoneMask),
13974
14014
  onChange: detail => handleMaskChange(detail),
13975
- onKeyPress: inPlace ? detail => handleKeyPress(detail) : undefined,
14015
+ onKeyPress: detail => handleKeyPress(detail),
13976
14016
  width: "100%"
13977
14017
  })
13978
14018
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abgov/jsonforms-components",
3
- "version": "2.75.1",
3
+ "version": "2.75.3",
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,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-09-08T16:38:31.930Z",
4
- "sourceCommit": "769e0871a07d4049577cd0a6f97d1dc56258481b",
3
+ "generatedAt": "2026-09-10T20:48:43.882Z",
4
+ "sourceCommit": "5a1d458e5df0177e45371e7770719fd29fb0b773",
5
5
  "sourcePath": "libs/jsonforms-components/src/index.ts",
6
6
  "rendererCount": 33,
7
7
  "renderers": [
@@ -5,6 +5,20 @@ export interface MaskPattern {
5
5
  allowedKeys?: RegExp;
6
6
  }
7
7
  export declare const DEFAULT_PATTERNS: Record<string, MaskPattern>;
8
+ export declare const resolveFormatConfig: (schema?: {
9
+ format?: string;
10
+ title?: string;
11
+ }) => MaskPattern | undefined;
12
+ type SchemaErrorMessage = string | {
13
+ pattern?: string;
14
+ format?: string;
15
+ };
16
+ type FormatSchema = {
17
+ format?: string;
18
+ title?: string;
19
+ errorMessage?: SchemaErrorMessage;
20
+ };
21
+ export declare const getConfiguredFormatError: (schema?: FormatSchema) => string | undefined;
8
22
  export declare const MASK_PLACEHOLDERS: Set<string>;
9
23
  export declare const MASK_CONTROL_KEYS: Set<string>;
10
24
  export declare const shouldBlockKey: (key: string, allowedKeys?: RegExp) => boolean;
@@ -10,6 +10,7 @@ interface UseMaskedInputOptions {
10
10
  mask: string;
11
11
  inPlace: boolean;
12
12
  data: unknown;
13
+ allowedKeys?: RegExp;
13
14
  onCommit: (stored: string) => void;
14
15
  }
15
16
  interface UseMaskedInputResult {
@@ -17,5 +18,5 @@ interface UseMaskedInputResult {
17
18
  handleChange: (detail: MaskChangeDetail) => void;
18
19
  handleKeyPress: (detail: MaskKeyPressDetail) => void;
19
20
  }
20
- export declare const useMaskedInput: ({ mask, inPlace, data, onCommit }: UseMaskedInputOptions) => UseMaskedInputResult;
21
+ export declare const useMaskedInput: ({ mask, inPlace, data, allowedKeys, onCommit, }: UseMaskedInputOptions) => UseMaskedInputResult;
21
22
  export {};