Skip to content

Commit

Permalink
✨ Pretend that inputMask is supported
Browse files Browse the repository at this point in the history
inputMask is used under the hood by the postcode component. The formio
builder implementation stops at the placeholder. Since these components
are not actually used in the renderer/SDK and are only set up for
preview purposes and it's *too much* work to get the masking hooked
up, I've decided to let it be.
  • Loading branch information
sergei-maertens committed Oct 18, 2023
1 parent fcc665a commit 09ccef5
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 0 deletions.
32 changes: 32 additions & 0 deletions src/components/formio/textfield.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,35 @@ export const WithErrors: Story = {
await expect(canvas.queryByText('Example error')).toBeInTheDocument();
},
};

export const WithMask: Story = {
args: {
label: 'With mask',
inputMask: '9999 AA',
},

parameters: {
formik: {
initialValues: {'my-textfield': ''},
},
},

play: async ({canvasElement, step}) => {
const canvas = within(canvasElement);
const input = canvas.getByLabelText<HTMLInputElement>('With mask');

await step('Empty input shows placeholder', async () => {
expect(input).toHaveDisplayValue('');
expect(input).toHaveAttribute('placeholder', '____ __');
});

await step('Typing into input', async () => {
await userEvent.type(input, '1015');
// with formio's masking enabled, this would be '1015 __', but we're skipping
// that messy implementation for the form builder. At some point we should be
// able to re-use renderer components that fully implement the behaviour in an
// accessible manner.
expect(input).toHaveDisplayValue('1015');
});
},
};
13 changes: 13 additions & 0 deletions src/components/formio/textfield.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {useContext, useRef} from 'react';
import {RenderContext} from '@/context';
import CharCount from '@/utils/charcount';
import {ErrorList, useValidationErrors} from '@/utils/errors';
import {applyInputMask} from '@/utils/inputmask';

import Component from './component';
import Description from './description';
Expand All @@ -17,6 +18,7 @@ export interface TextFieldProps {
tooltip?: string;
description?: string;
showCharCount?: boolean;
inputMask?: string;
}

export const TextField: React.FC<JSX.IntrinsicElements['input'] & TextFieldProps> = ({
Expand All @@ -26,6 +28,7 @@ export const TextField: React.FC<JSX.IntrinsicElements['input'] & TextFieldProps
tooltip = '',
description = '',
showCharCount = false,
inputMask,
...props
}) => {
const {getFieldProps, getFieldMeta} = useFormikContext();
Expand All @@ -41,6 +44,16 @@ export const TextField: React.FC<JSX.IntrinsicElements['input'] & TextFieldProps
props = {...props, value: ''};
}

// XXX: this is only accepted in the form builder to get to (close to) vanilla form
// builder feature parity - setting the value with mask placeholders is bad for
// accessibility.
//
// It also turns out to be too much effort to get the masking working for preview
// purposes, so that is deliberately "not working".
if (!props.value && inputMask && !props.placeholder) {
props.placeholder = applyInputMask('', inputMask);
}

const inputField = (
<Field
innerRef={inputRef}
Expand Down
23 changes: 23 additions & 0 deletions src/utils/inputmask.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* This is Formio.js 4.13.x's input mask behaviour.
*
* It is known to have accessibility issues because the value is set, causing some
* screenreaders to read out the placeholders. For more information, see
* https://giovanicamara.com/blog/accessible-input-masking/
*
* This functionality only exists here to achieve feature parity with the native
* form builder, for the actual SDK/formio renderer, a better solution needs to be
* found.
*
* @deprecated Do not use this (anymore) for user-facing forms.
*
*/
// @ts-ignore there are no type definitions
import {conformToMask} from '@formio/vanilla-text-mask';
import {Utils} from 'formiojs';

export const applyInputMask = (textValue: string, mask: string) => {
const options = {placeholderChar: '_'};
const result = conformToMask(textValue, Utils.getInputMask(mask), options);
return result.conformedValue;
};

0 comments on commit 09ccef5

Please sign in to comment.