Skip to content

Commit

Permalink
refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
riccardoperra committed Nov 25, 2023
1 parent 6c4661d commit a122f0f
Show file tree
Hide file tree
Showing 8 changed files with 429 additions and 103 deletions.
4 changes: 2 additions & 2 deletions packages/kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@
"@kobalte/core": "^0.11.0",
"@kobalte/utils": "^0.9.0",
"@kobalte/vanilla-extract": "^0.4.0",
"@maskito/core": "^1.4.0",
"@maskito/kit": "^1.4.0",
"@maskito/core": "^1.9.0",
"@maskito/kit": "^1.9.0",
"@motionone/solid": "^10.16.0",
"@radix-ui/colors": "^0.1.8",
"@solid-primitives/pagination": "^0.2.5",
Expand Down
72 changes: 32 additions & 40 deletions packages/kit/src/components/NumberField/NumberField.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,14 @@
import { createControllableSignal, TextField as KTextField } from "@kobalte/core";
import { clamp, isNumber, mergeRefs } from "@kobalte/utils";
import { clamp, mergeRefs } from "@kobalte/utils";
import {
maskitoCaretGuard,
maskitoNumberOptionsGenerator,
maskitoParseNumber,
} from "@maskito/kit";
import {
createEffect,
JSX,
mergeProps,
on,
onMount,
Ref,
Show,
splitProps,
} from "solid-js";
import { ChevronDownIcon } from "../../icons/ChevronDownIcon";
import { ChevronUpIcon } from "../../icons/ChevronUpIcon";
import { JSX, mergeProps, onMount, Ref, Show, splitProps } from "solid-js";
import { SlotProp } from "../../utils/component";
import { mergeClasses } from "../../utils/css";
import { createMaskito } from "../../utils/maskito";
import { Button } from "../Button/Button";
import { BaseFieldProps, createBaseFieldProps } from "../Field/createBaseFieldProps";
import {
createFieldErrorMessageProps,
Expand All @@ -33,6 +21,7 @@ import { NumberFieldMessage } from "./NumberFieldMessage";
import type { InputNumberOptions } from "./options";
import { defaultNumberFormat, INPUT_NUMBER_OPTIONS as defaultOptions } from "./options";
import { CHAR_HYPHEN, CHAR_MINUS } from "./unicodeCharacters";
import { NumberFieldControls } from "./NumberFieldControls";

// TODO: add to base field slot that respect the BaseFieldProps signature?
type TextFieldSlot = "root" | "input" | "label" | "errorLabel";
Expand All @@ -58,7 +47,7 @@ export type NumberFieldProps = NumberFieldRootOptions &

export function NumberField(props: NumberFieldProps) {
const [local, options, state, others] = splitProps(
props,
mergeProps({ size: "md" }, props),
[
"description",
"size",
Expand All @@ -73,6 +62,7 @@ export function NumberField(props: NumberFieldProps) {
["value", "onChange", "defaultValue"],
);

let unfinishedValue: string | null = null;
const [value, setValue] = createControllableSignal<number | undefined>({
value: () => state.value,
defaultValue: () => state.defaultValue,
Expand Down Expand Up @@ -150,22 +140,41 @@ export function NumberField(props: NumberFieldProps) {
setValue(clamp((value() || 0) - optionsWithDefault.step, computeMin(), computeMax()));
};

const isNativeValueNotFinished = (): boolean => {
const nativeNumberValue = maskitoParseNumber(
formattedValue(),
defaultNumberFormat.decimalSeparator,
);
return nativeNumberValue < 0
? nativeNumberValue > computeMax()
: nativeNumberValue < computeMin();
};

const onValueChange = (nativeValue: string) => {
const parsedValue = maskitoParseNumber(
nativeValue,
defaultNumberFormat.decimalSeparator,
);

unfinishedValue = null;

if (Number.isNaN(parsedValue)) {
setValue(undefined);
return;
}

if (isNativeValueNotFinished()) {
console.log("is unfinished");
unfinishedValue = nativeValue;
return;
}

if (parsedValue < computeMin() || parsedValue > computeMax()) {
return;
}

setValue(parsedValue);
// todo: fix
internalRef.value = formattedValue();
};

Expand All @@ -186,8 +195,8 @@ export function NumberField(props: NumberFieldProps) {
};

const onFocused = (focused: boolean) => {
const nativeNumberValue = value()
? maskitoParseNumber(String(value() ?? ""), defaultNumberFormat.decimalSeparator)
const nativeNumberValue = unfinishedValue
? maskitoParseNumber(unfinishedValue, defaultNumberFormat.decimalSeparator)
: value();

if (Number.isNaN(nativeNumberValue)) {
Expand All @@ -207,7 +216,7 @@ export function NumberField(props: NumberFieldProps) {
return (
<KTextField.Root
data-cui={"number-field"}
data-field-size={local.size}
data-field-size={local.size ?? "md"}
class={mergeClasses(styles.baseFieldContainer, local?.slotClasses?.root)}
value={value() as unknown as string}
onChange={onValueChange}
Expand All @@ -230,28 +239,11 @@ export function NumberField(props: NumberFieldProps) {
onFocus={() => onFocused(true)}
/>

<div class={styles.controlsContainer}>
<Button
type={"button"}
variant={"ghost"}
theme={"secondary"}
aria-label={`Increment by ${optionsWithDefault.step}`}
class={styles.controlButton}
onClick={increment}
>
<ChevronUpIcon class={styles.control} />
</Button>
<Button
type={"button"}
variant={"ghost"}
theme={"secondary"}
aria-label={`Decrement by ${optionsWithDefault.step}`}
class={styles.controlButton}
onClick={decrement}
>
<ChevronDownIcon class={styles.control} />
</Button>
</div>
<NumberFieldControls
increment={increment}
decrement={decrement}
step={optionsWithDefault.step}
/>
</div>

<Show when={local.description} keyed={false}>
Expand Down
36 changes: 36 additions & 0 deletions packages/kit/src/components/NumberField/NumberFieldControls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as styles from "./NumberField.css";
import { Button } from "../Button/Button";
import { ChevronDownIcon, ChevronUpIcon } from "../../icons";

interface NumberFieldControlsProps {
increment: () => void;
decrement: () => void;
step: number;
}

export function NumberFieldControls(props: NumberFieldControlsProps) {
return (
<div class={styles.controlsContainer}>
<Button
type={"button"}
variant={"ghost"}
theme={"secondary"}
aria-label={`Increment by ${props.step}`}
class={styles.controlButton}
onClick={props.increment}
>
<ChevronUpIcon class={styles.control} />
</Button>
<Button
type={"button"}
variant={"ghost"}
theme={"secondary"}
aria-label={`Decrement by ${props.step}`}
class={styles.controlButton}
onClick={props.decrement}
>
<ChevronDownIcon class={styles.control} />
</Button>
</div>
);
}
2 changes: 1 addition & 1 deletion packages/kit/src/components/NumberField/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const INPUT_NUMBER_OPTIONS: InputNumberOptions = {
min: Number.MIN_SAFE_INTEGER,
max: Number.MAX_SAFE_INTEGER,
step: 1,
precision: 2,
precision: 0,
prefix: "",
postfix: "",
};
Expand Down
14 changes: 14 additions & 0 deletions packages/kit/src/components/NumberField/round.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
/*
*
* Portions of this file are based on code from Taiga UI.
* Copyright 2020 Tinkoff Bank
*
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
const MAX_PRECISION = 292;

export function tuiRound(value: number, precision: number = 0): number {
Expand Down
16 changes: 1 addition & 15 deletions packages/kit/src/utils/maskito.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,13 @@ import {
} from "@maskito/core";
import { createEffect, createSignal, onCleanup, Setter } from "solid-js";

/**
* Hook for convenient use of Maskito in React
* @description For controlled inputs use `onInput` event
* @param options options used for creating Maskito
* @param elementPredicate function that can help find nested Input or TextArea
* @returns ref callback to pass it in React Element
* @example
* // To avoid unnecessary hook runs with Maskito recreation pass named variables
* // good example ✅
* useMaskito({ options: maskitoOptions, elementPredicate: maskitoPredicate })
*
* // bad example ❌
* useMaskito({ options: { mask: /^.*$/ }, elementPredicate: () => e.querySelector('input') })
*/
export const createMaskito = ({
options = MASKITO_DEFAULT_OPTIONS,
elementPredicate = MASKITO_DEFAULT_ELEMENT_PREDICATE,
}: {
options?: MaybeAccessor<MaskitoOptions>;
elementPredicate?: MaybeAccessor<MaskitoElementPredicate>;
} = {}): Setter<HTMLElement> => {
} = {}): Setter<HTMLElement | null> => {
const [ref, setRef] = createSignal<HTMLElement | null>(null);

createEffect(() => {
Expand Down
Loading

0 comments on commit a122f0f

Please sign in to comment.