@abgov/jsonforms-components 2.75.0 → 2.75.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.esm.js +286 -207
- package/package.json +1 -1
- package/renderer-catalog.json +2 -2
- package/src/lib/util/patternForm.d.ts +15 -0
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
|
-
|
|
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,166 +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
|
-
// Extracts the underlying input element from a GoA change/keypress detail, if available.
|
|
4932
|
-
const getMaskInputTarget = detail => {
|
|
4933
|
-
var _detail$event$target, _detail$event;
|
|
4934
|
-
return (_detail$event$target = (_detail$event = detail.event) == null ? void 0 : _detail$event.target) != null ? _detail$event$target : undefined;
|
|
4935
|
-
};
|
|
4936
|
-
// Applies an in-place edit to the DOM: forces the (fixed-length) value and restores the caret after React commits.
|
|
4937
|
-
const applyInPlaceEdit = (target, edit) => {
|
|
4938
|
-
if (!target) {
|
|
4939
|
-
return;
|
|
4940
|
-
}
|
|
4941
|
-
target.value = edit.display;
|
|
4942
|
-
if (target.setSelectionRange) {
|
|
4943
|
-
requestAnimationFrame(() => target.setSelectionRange == null ? void 0 : target.setSelectionRange(edit.caret, edit.caret));
|
|
4944
|
-
}
|
|
4945
|
-
};
|
|
4946
|
-
/**
|
|
4947
|
-
* Applies a mask template to a value, e.g. mask "(###) ###-####" turns "7801234567" into "(780) 123-4567".
|
|
4948
|
-
* '#' consumes one content character from the input; every other character is a literal inserted into the output.
|
|
4949
|
-
* The mask's literal characters are stripped from the input first so an already formatted value re-formats cleanly.
|
|
4950
|
-
* Returns the original value unchanged when the mask is empty or no content characters remain.
|
|
4951
|
-
* @param value - The raw input value
|
|
4952
|
-
* @param mask - The mask template string
|
|
4953
|
-
* @returns The masked value
|
|
4954
|
-
*/
|
|
4955
|
-
const applyFormatPattern = (value, mask) => {
|
|
4956
|
-
if (!mask || !value) {
|
|
4957
|
-
return value;
|
|
4958
|
-
}
|
|
4959
|
-
const literals = new Set(mask.split('').filter(char => !isPlaceholder(char)));
|
|
4960
|
-
const content = value.split('').filter(char => !literals.has(char));
|
|
4961
|
-
if (content.length === 0) {
|
|
4962
|
-
return value;
|
|
4963
|
-
}
|
|
4964
|
-
let contentIndex = 0;
|
|
4965
|
-
let result = '';
|
|
4966
|
-
for (const maskChar of mask) {
|
|
4967
|
-
if (contentIndex >= content.length) {
|
|
4968
|
-
break;
|
|
4969
|
-
}
|
|
4970
|
-
if (isPlaceholder(maskChar)) {
|
|
4971
|
-
result += content[contentIndex];
|
|
4972
|
-
contentIndex += 1;
|
|
4973
|
-
} else {
|
|
4974
|
-
result += maskChar;
|
|
4975
|
-
}
|
|
4976
|
-
}
|
|
4977
|
-
// Append any remaining content characters that did not fit the mask.
|
|
4978
|
-
if (contentIndex < content.length) {
|
|
4979
|
-
result += content.slice(contentIndex).join('');
|
|
4980
|
-
}
|
|
4981
|
-
return result;
|
|
4982
|
-
};
|
|
4983
|
-
|
|
4984
5075
|
/**
|
|
4985
5076
|
* Checks input controls data value to determine is required and has any data.
|
|
4986
5077
|
* @param props - The JsonForm control props
|
|
@@ -7573,38 +7664,6 @@ function buildConditionalDeps(rootSchema) {
|
|
|
7573
7664
|
return out;
|
|
7574
7665
|
}
|
|
7575
7666
|
|
|
7576
|
-
const sinTitle = 'Social insurance number';
|
|
7577
|
-
const invalidSin = 'Social insurance number is invalid';
|
|
7578
|
-
const DEFAULT_MAX_ITEMS = 50;
|
|
7579
|
-
const REQUIRED_PROPERTY_ERROR = 'is a required property';
|
|
7580
|
-
const ADDRESS_LOOKUP_LABELS = {
|
|
7581
|
-
addressLine1: 'Address line 1',
|
|
7582
|
-
addressLine2: 'Address line 2',
|
|
7583
|
-
municipality: 'City',
|
|
7584
|
-
postalCode: 'Postal code',
|
|
7585
|
-
subdivisionCode: 'Province',
|
|
7586
|
-
country: 'Country'
|
|
7587
|
-
};
|
|
7588
|
-
const VALIDATION_KEYWORDS = {
|
|
7589
|
-
REQUIRED: 'required',
|
|
7590
|
-
MIN_LENGTH: 'minLength',
|
|
7591
|
-
MAX_LENGTH: 'maxLength',
|
|
7592
|
-
FORMAT: 'format',
|
|
7593
|
-
MINIMUM: 'minimum',
|
|
7594
|
-
MAXIMUM: 'maximum',
|
|
7595
|
-
TYPE: 'type',
|
|
7596
|
-
ERROR_MESSAGE: 'errorMessage'
|
|
7597
|
-
};
|
|
7598
|
-
var StepStatus;
|
|
7599
|
-
(function (StepStatus) {
|
|
7600
|
-
StepStatus["NOT_STARTED"] = "NotStarted";
|
|
7601
|
-
StepStatus["IN_PROGRESS"] = "InProgress";
|
|
7602
|
-
StepStatus["COMPLETED"] = "Completed";
|
|
7603
|
-
})(StepStatus || (StepStatus = {}));
|
|
7604
|
-
const getAddressLookupFieldLabel = fieldName => {
|
|
7605
|
-
return ADDRESS_LOOKUP_LABELS[fieldName] || fieldName;
|
|
7606
|
-
};
|
|
7607
|
-
|
|
7608
7667
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7609
7668
|
function hasMeaningfulValue(val) {
|
|
7610
7669
|
if (val === undefined || val === null) return false;
|
|
@@ -10007,13 +10066,6 @@ const resetInputValue = (detail, value) => {
|
|
|
10007
10066
|
target.value = value;
|
|
10008
10067
|
}
|
|
10009
10068
|
};
|
|
10010
|
-
// Resolve the mask format for a field: schema.format must match a DEFAULT_PATTERNS key.
|
|
10011
|
-
const resolveFormatConfig = schema => {
|
|
10012
|
-
if (schema.format && schema.format in DEFAULT_PATTERNS) {
|
|
10013
|
-
return DEFAULT_PATTERNS[schema.format];
|
|
10014
|
-
}
|
|
10015
|
-
return schema.title === sinTitle ? DEFAULT_PATTERNS.sin : undefined;
|
|
10016
|
-
};
|
|
10017
10069
|
const GoAInputText = props => {
|
|
10018
10070
|
return jsxs(JsonFormRegisterProvider, {
|
|
10019
10071
|
defaultRegisters: undefined,
|
|
@@ -10171,6 +10223,11 @@ const InnerGoAInputText = props => {
|
|
|
10171
10223
|
var _target$selectionStar;
|
|
10172
10224
|
const target = getMaskInputTarget(detail);
|
|
10173
10225
|
const caretIndex = (_target$selectionStar = target == null ? void 0 : target.selectionStart) != null ? _target$selectionStar : cleaned.length;
|
|
10226
|
+
const overflow = overflowMaskEdit(localValue, cleaned, caretIndex, mask);
|
|
10227
|
+
if (overflow) {
|
|
10228
|
+
applyInPlaceEdit(target, overflow);
|
|
10229
|
+
return;
|
|
10230
|
+
}
|
|
10174
10231
|
const edit = computeMaskEdit(cleaned, caretIndex, mask);
|
|
10175
10232
|
applyInPlaceEdit(target, edit);
|
|
10176
10233
|
setLocalValue(edit.display);
|
|
@@ -10205,12 +10262,22 @@ const InnerGoAInputText = props => {
|
|
|
10205
10262
|
});
|
|
10206
10263
|
},
|
|
10207
10264
|
onKeyPress: detail => {
|
|
10265
|
+
const keyPressDetail = detail;
|
|
10208
10266
|
const blockDisallowed = shouldBlockKey(detail.key, allowedKeys);
|
|
10209
|
-
// In-place has no
|
|
10267
|
+
// In-place has no maxLength, and GoA keyPress fires on keyup after the character is inserted.
|
|
10210
10268
|
const blockOverflow = inPlace && !!mask && /^[A-Za-z0-9]$/.test(detail.key) && isMaskFilled(localValue, mask);
|
|
10211
10269
|
if (blockDisallowed || blockOverflow) {
|
|
10212
|
-
var
|
|
10213
|
-
(
|
|
10270
|
+
var _keyPressDetail$event;
|
|
10271
|
+
(_keyPressDetail$event = keyPressDetail.event) == null || _keyPressDetail$event.preventDefault();
|
|
10272
|
+
}
|
|
10273
|
+
if (blockOverflow && mask) {
|
|
10274
|
+
var _ref, _target$value, _target$selectionStar2;
|
|
10275
|
+
const target = getMaskInputTarget(keyPressDetail);
|
|
10276
|
+
const rawValue = (_ref = (_target$value = target == null ? void 0 : target.value) != null ? _target$value : detail.value) != null ? _ref : localValue;
|
|
10277
|
+
const overflow = overflowMaskEdit(localValue, rawValue, (_target$selectionStar2 = target == null ? void 0 : target.selectionStart) != null ? _target$selectionStar2 : localValue.length, mask);
|
|
10278
|
+
if (overflow) {
|
|
10279
|
+
applyInPlaceEdit(target, overflow);
|
|
10280
|
+
}
|
|
10214
10281
|
}
|
|
10215
10282
|
}
|
|
10216
10283
|
}, uischema == null || (_uischema$options10 = uischema.options) == null ? void 0 : _uischema$options10.componentProps))
|
|
@@ -13850,16 +13917,28 @@ const useMaskedInput = ({
|
|
|
13850
13917
|
}
|
|
13851
13918
|
const target = getMaskInputTarget(detail);
|
|
13852
13919
|
const caretIndex = (_target$selectionStar = target == null ? void 0 : target.selectionStart) != null ? _target$selectionStar : rawValue.length;
|
|
13920
|
+
const overflow = overflowMaskEdit(value, rawValue, caretIndex, mask);
|
|
13921
|
+
if (overflow) {
|
|
13922
|
+
applyInPlaceEdit(target, overflow);
|
|
13923
|
+
return;
|
|
13924
|
+
}
|
|
13853
13925
|
const edit = computeMaskEdit(rawValue, caretIndex, mask);
|
|
13854
13926
|
applyInPlaceEdit(target, edit);
|
|
13855
13927
|
setValue(edit.display);
|
|
13856
13928
|
onCommit(edit.stored);
|
|
13857
13929
|
};
|
|
13858
|
-
// In-place
|
|
13930
|
+
// In-place has no maxLength (the template is already full length) and GoA keyPress is keyup, so restore on overflow.
|
|
13859
13931
|
const handleKeyPress = detail => {
|
|
13860
|
-
|
|
13861
|
-
|
|
13862
|
-
|
|
13932
|
+
var _detail$event, _target$value, _target$selectionStar2;
|
|
13933
|
+
if (!inPlace || !/^[A-Za-z0-9]$/.test(detail.key) || !isMaskFilled(value, mask)) {
|
|
13934
|
+
return;
|
|
13935
|
+
}
|
|
13936
|
+
(_detail$event = detail.event) == null || _detail$event.preventDefault();
|
|
13937
|
+
const target = getMaskInputTarget(detail);
|
|
13938
|
+
const rawValue = (_target$value = target == null ? void 0 : target.value) != null ? _target$value : value;
|
|
13939
|
+
const overflow = overflowMaskEdit(value, rawValue, (_target$selectionStar2 = target == null ? void 0 : target.selectionStart) != null ? _target$selectionStar2 : value.length, mask);
|
|
13940
|
+
if (overflow) {
|
|
13941
|
+
applyInPlaceEdit(target, overflow);
|
|
13863
13942
|
}
|
|
13864
13943
|
};
|
|
13865
13944
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abgov/jsonforms-components",
|
|
3
|
-
"version": "2.75.
|
|
3
|
+
"version": "2.75.2",
|
|
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",
|
package/renderer-catalog.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
|
-
"generatedAt": "2026-09-
|
|
4
|
-
"sourceCommit": "
|
|
3
|
+
"generatedAt": "2026-09-09T19:26:19.202Z",
|
|
4
|
+
"sourceCommit": "7fb9064678498d4c0d4a0cc3926968d141a9f242",
|
|
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;
|
|
@@ -28,6 +42,7 @@ export declare const getMaskInputTarget: (detail: {
|
|
|
28
42
|
event?: Event;
|
|
29
43
|
}) => MaskInputTarget;
|
|
30
44
|
export declare const applyInPlaceEdit: (target: MaskInputTarget, edit: MaskEdit) => void;
|
|
45
|
+
export declare const overflowMaskEdit: (previousDisplay: string, rawValue: string, caretIndex: number, mask: string) => MaskEdit | undefined;
|
|
31
46
|
/**
|
|
32
47
|
* Applies a mask template to a value, e.g. mask "(###) ###-####" turns "7801234567" into "(780) 123-4567".
|
|
33
48
|
* '#' consumes one content character from the input; every other character is a literal inserted into the output.
|