Skip to content

Commit

Permalink
provide default values for hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
uNmAnNeR committed Apr 9, 2024
1 parent 8b4944f commit 67a8fd2
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 78 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.cjs
Expand Up @@ -19,6 +19,7 @@ module.exports = {
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'warn',
'@typescript-eslint/no-empty-function': 'warn',
'no-empty': ["error", { "allowEmptyCatch": true }],
'no-empty': ['error', { allowEmptyCatch: true }],
'prefer-const': ['error', { destructuring: 'all' }],
},
};
116 changes: 73 additions & 43 deletions packages/react-imask/src/hook.ts
Expand Up @@ -9,10 +9,20 @@ function useIMask<
Opts extends FactoryOpts=FactoryOpts,
>(
opts: Opts,
{ onAccept, onComplete, ref=useRef<MaskElement | null>(null) }: {
{
onAccept,
onComplete,
ref=useRef<MaskElement | null>(null),
defaultValue,
defaultUnmaskedValue,
defaultTypedValue,
}: {
ref?: MutableRefObject<MaskElement | null>,
onAccept?: (value: InputMask<Opts>['value'], maskRef: InputMask<Opts>, e?: InputEvent) => void;
onComplete?: (value: InputMask<Opts>['value'], maskRef: InputMask<Opts>, e?: InputEvent) => void;
defaultValue?: InputMask<Opts>['value'],
defaultUnmaskedValue?: InputMask<Opts>['unmaskedValue'],
defaultTypedValue?: InputMask<Opts>['typedValue'],
} = {}
): {
ref: MutableRefObject<MaskElement | null>,
Expand All @@ -25,7 +35,6 @@ function useIMask<
setTypedValue: Dispatch<InputMask<Opts>['typedValue']>,
} {
const maskRef = useRef<InputMask<Opts> | null>(null);
const [initialized, setInitialized] = useState<boolean>(false);
const [lastAcceptState, setLastAcceptState] = useState<{
value?: InputMask<Opts>['value'],
unmaskedValue?: InputMask<Opts>['unmaskedValue'],
Expand All @@ -36,34 +45,79 @@ function useIMask<
const [typedValue, setTypedValue] = useState<InputMask<Opts>['typedValue']>();

const _destroyMask = useCallback(() => {
if (!initialized) return;
maskRef.current?.destroy();
maskRef.current = null;
}, []);

const storeLastAcceptedValues = useCallback(() => {
const m = maskRef.current;
if (!m) return;

setLastAcceptState({
value: m.value,
unmaskedValue: m.unmaskedValue,
typedValue: m.typedValue,
});

setTypedValue(m.typedValue);
setUnmaskedValue(m.unmaskedValue);
setValue(m.value);
}, []);

const _onAccept = useCallback(
(event?: InputEvent) => {
const m = maskRef.current;
if (!m) return;

setLastAcceptState({
value: m.value,
unmaskedValue: m.unmaskedValue,
typedValue: m.typedValue,
});
setTypedValue(m.typedValue);
setUnmaskedValue(m.unmaskedValue);
setValue(m.value);
storeLastAcceptedValues();

onAccept?.(m.value, m, event);
},
[onAccept],
);
}, [onAccept]);

const _onComplete = useCallback(
(event?: InputEvent) => maskRef.current && onComplete?.(maskRef.current.value, maskRef.current, event),
[onComplete],
);

useEffect(() => {
const { value: lastAcceptValue, ...state } = lastAcceptState;
const mask = maskRef.current;

if (!mask || value === undefined) return;

if (lastAcceptValue !== value) {
mask.value = value;
if (mask.value !== value) _onAccept();
}
setLastAcceptState(state);
}, [value]);

useEffect(() => {
const { unmaskedValue: lastAcceptUnmaskedValue, ...state } = lastAcceptState;
const mask = maskRef.current;

if (!mask || unmaskedValue === undefined) return;

if (lastAcceptUnmaskedValue !== unmaskedValue) {
mask.unmaskedValue = unmaskedValue;
if (mask.unmaskedValue !== unmaskedValue) _onAccept();
}
setLastAcceptState(state);
}, [unmaskedValue]);

useEffect(() => {
const { typedValue: lastAcceptTypedValue, ...state } = lastAcceptState;
const mask = maskRef.current;

if (!mask || typedValue === undefined) return;

if (lastAcceptTypedValue !== typedValue) {
mask.typedValue = typedValue;
if (!mask.masked.typedValueEquals(typedValue)) _onAccept();
}
setLastAcceptState(state);
}, [typedValue]);

useEffect(() => {
const el = ref.current;

Expand All @@ -74,12 +128,15 @@ function useIMask<
if (!mask) {
if (el && opts?.mask) {
maskRef.current = IMask(el, opts);
_onAccept();
storeLastAcceptedValues();

if (defaultValue !== undefined) setValue(defaultValue);
if (defaultUnmaskedValue !== undefined) setUnmaskedValue(defaultUnmaskedValue);
if (defaultTypedValue !== undefined) setTypedValue(defaultTypedValue);
}
} else {
mask?.updateOptions(opts as any); // TODO fix no idea
}
setInitialized(Boolean(maskRef.current));
}, [opts, _destroyMask, _onAccept]);

useEffect(() => {
Expand All @@ -96,33 +153,6 @@ function useIMask<
};
}, [_onAccept, _onComplete]);

useEffect(() => {
const { value: lastAcceptValue, ...state } = lastAcceptState;
const mask = maskRef.current;
if (mask && initialized) {
if (lastAcceptValue !== value) mask.value = value;
setLastAcceptState(state);
}
}, [value]);

useEffect(() => {
const { unmaskedValue: lastAcceptUnmaskedValue, ...state } = lastAcceptState;
const mask = maskRef.current;
if (mask && initialized) {
if (lastAcceptUnmaskedValue !== unmaskedValue) mask.unmaskedValue = unmaskedValue;
setLastAcceptState(state);
}
}, [unmaskedValue]);

useEffect(() => {
const { typedValue: lastAcceptTypedValue, ...state } = lastAcceptState;
const mask = maskRef.current;
if (mask && initialized) {
if (lastAcceptTypedValue !== typedValue) mask.typedValue = typedValue;
setLastAcceptState(state);
}
}, [typedValue]);

useEffect(() => _destroyMask, [_destroyMask]);

return {
Expand Down
18 changes: 16 additions & 2 deletions packages/vue-imask/example.html
Expand Up @@ -19,7 +19,7 @@ <h1>Vue IMask Plugin Demo</h1>
<label>
Component
<imask-input
v-model:typed="numberModel"
v-model:typed="numberValue"
:mask="Number"
radix="."
@accept:masked="onAcceptNumberMasked"
Expand All @@ -31,6 +31,9 @@ <h1>Vue IMask Plugin Demo</h1>
<button
@click="toggleNumberMask"
>{{numberMask ? 'Disable' : 'Enable'}} mask</button>

<br>
<input ref="el">
</main>

<script src="https://unpkg.com/vue"></script>
Expand All @@ -55,8 +58,19 @@ <h1>Vue IMask Plugin Demo</h1>

numberMask: testNumberMask,
numberUnmask: 'typed',
numberValue: 0,
numberValue: 10,
}),
setup () {
const { el, typed } = VueIMask.useIMask({
mask: Number,
scale: 0,
}, {
defaultTypedValue: 500,
onAccept: () => console.log('accept composition value', typed.value),
});

return { el };
},
methods: {
onAcceptDate: function (e) {
console.log('accept date', e.detail.value);
Expand Down
74 changes: 42 additions & 32 deletions packages/vue-imask/src/composable.ts
Expand Up @@ -20,13 +20,18 @@ type ComposableParams<Opts extends FactoryOpts> = {
emit?: <E extends ComposableEmitEvent>(eventName: E, value: ComposableEmitValue<E, Opts>, e?: InputEvent) => void,
onAccept?: (e?: InputEvent) => void,
onComplete?: (e?: InputEvent) => void,
defaultValue?: InputMask<Opts>['value'],
defaultUnmaskedValue?: InputMask<Opts>['unmaskedValue'],
defaultTypedValue?: InputMask<Opts>['typedValue'],
}

export default
function useIMask<
MaskElement extends InputMaskElement,
Opts extends FactoryOpts
> (props: Opts | Ref<Opts>, { emit, onAccept, onComplete }: ComposableParams<Opts>={}): {
> (props: Opts | Ref<Opts>, {
emit, onAccept, onComplete, defaultValue, defaultUnmaskedValue, defaultTypedValue,
}: ComposableParams<Opts>={}): {
el: Ref<MaskElement | undefined>,
mask: DeepReadonly<Ref<InputMask<Opts> | undefined>>,
masked: Ref<InputMask<Opts>['value']>,
Expand Down Expand Up @@ -59,7 +64,7 @@ function useIMask<
emit('accept:typed', typed.value, event);
emit('accept:unmasked', unmasked.value, event);
}
if (onAccept) onAccept(event);
onAccept?.(event);
}

function _onComplete (event?: InputEvent) {
Expand All @@ -69,36 +74,11 @@ function useIMask<
emit('complete:typed', (mask.value as InputMask<Opts>).typedValue, event);
emit('complete:unmasked', (mask.value as InputMask<Opts>).unmaskedValue, event);
}
if (onComplete) onComplete(event);
onComplete?.(event);
}

function _initMask () {
$el = el.value;
const $props = _props.value;

if (!$el || !$props?.mask) return;

mask.value = IMask($el, $props)
.on('accept', _onAccept)
.on('complete', _onComplete);

updateUnmaskedValue();
updateMaskedValue();
updateTypedValue();

storeLastAcceptedValues();
}

function _destroyMask () {
mask.value?.destroy();
mask.value = undefined;
}

onMounted(_initMask);
onUnmounted(_destroyMask);

const updateUnmaskedValue = () => {
if (!mask.value) return;
if (!mask.value || unmasked.value === undefined) return;

if ($lastAcceptedUnmaskedValue !== unmasked.value) {
mask.value.unmaskedValue = unmasked.value;
Expand All @@ -109,7 +89,7 @@ function useIMask<
watch(unmasked, updateUnmaskedValue);

const updateMaskedValue = () => {
if (!mask.value) return;
if (!mask.value || masked.value === undefined) return;

if ($lastAcceptedValue !== masked.value) {
mask.value.value = masked.value;
Expand All @@ -120,16 +100,46 @@ function useIMask<
watch(masked, updateMaskedValue);

const updateTypedValue = () => {
if (!mask.value) return;
if (!mask.value || typed.value === undefined) return;

if ($lastAcceptedTypedValue !== typed.value) {
mask.value.typedValue = typed.value;
if (mask.value.typedValue !== typed.value) _onAccept();
if (!mask.value.masked.typedValueEquals(typed.value)) _onAccept();
}
$lastAcceptedTypedValue = undefined;
}
watch(typed, updateTypedValue);


function _initMask () {
$el = el.value;
const $props = _props.value;

if (!$el || !$props?.mask) return;

mask.value = IMask($el, $props);

if (defaultValue !== undefined) masked.value = defaultValue;
if (defaultUnmaskedValue !== undefined) unmasked.value = defaultUnmaskedValue;
if (defaultTypedValue !== undefined) typed.value = defaultTypedValue;

updateUnmaskedValue();
updateMaskedValue();
updateTypedValue();

storeLastAcceptedValues();

mask.value.on('accept', _onAccept).on('complete', _onComplete);
}

function _destroyMask () {
mask.value?.destroy();
mask.value = undefined;
}

onMounted(_initMask);
onUnmounted(_destroyMask);

watch([el, _props], () => {
const $newEl = el.value;
const $props = _props.value;
Expand Down

0 comments on commit 67a8fd2

Please sign in to comment.