Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/good-boats-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@milaboratories/milaboratories.monetization-test.ui": patch
"@milaboratories/milaboratories.ui-examples.ui": patch
"@milaboratories/uikit": patch
---

update uikit file/number input
6 changes: 3 additions & 3 deletions etc/blocks/monetization-test/ui/src/pages/MainPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ function onProductKeyInput(key: string) {
}
}
function validateProductKey(key: string): boolean | string {
function validateProductKey(key: string): string | undefined {
if (key.length > 0 && key.length !== PRODUCT_KEY_LENGTH) {
return "Invalid product key";
}
return true;
return undefined;
}
const dropdownOptions: ListOption<string>[] = [
Expand Down Expand Up @@ -142,7 +142,7 @@ const productOptions = [
:model-value="app.model.args.productKey"
label="or enter product key"
clearable
:rules="[validateProductKey]"
:validate="validateProductKey"
@update:model-value="onProductKeyInput"
/>
</PlContainer>
Expand Down
6 changes: 3 additions & 3 deletions etc/blocks/ui-examples/ui/src/pages/ErrorsPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ const data = reactive({
progressDurationMs: "10000",
});

function positiveNumberRule(v: string): boolean | string {
function positiveNumberRule(v: string): string | undefined {
const n = Number(v);
if (!Number.isFinite(n) || n <= 0) return "Must be a positive number";
return true;
return undefined;
}

const numbers = computed({
Expand Down Expand Up @@ -64,7 +64,7 @@ const numbers = computed({
<PlTextField
v-model="data.progressDurationMs"
label="Progress duration (ms)"
:rules="[positiveNumberRule]"
:validate="positiveNumberRule"
/>
</PlRow>
</PlBlockPage>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
import { reactive } from "vue";

const data = reactive({
text: "some text",
text: "some text" as string | undefined,
single: "A",
multiple: ["A", "B"],
multiple2: ["B", "A", "D"],
Expand Down Expand Up @@ -52,7 +52,7 @@ const options = listToOptions([
:compact="data.compactBtnGroup"
/>
<PlCheckbox v-model="data.compactBtnGroup">Compact btn group component</PlCheckbox>
<PlTextField v-model="data.text" label="PlTextField" clearable />
<PlTextField v-model="data.text" label="PlTextField" :clearable="() => undefined" />
<PlTextField v-model="data.text" label="PlTextField (password)" type="password" clearable />
<PlSearchField v-model="data.text">
<template #helper>
Expand Down
33 changes: 18 additions & 15 deletions etc/blocks/ui-examples/ui/src/pages/PlNumberFieldPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,26 @@ import { PlRow, PlContainer, PlCheckbox, PlNumberField, PlBlockPage } from "@pla
import { reactive } from "vue";

const data = reactive({
useIncrementButtons: true,
updateOnEnterOrClickOutside: false,
number: 100,
number: 100 as number | undefined,
disableSteps: false,
});

function updateNumber(value: number) {
if (data.number == null || Number.isNaN(data.number)) {
data.number = 0;
}
data.number += value;
}
</script>

<template>
<PlBlockPage>
<template #title>PlNumberField</template>
<pre>number: {{ data.number }} {{ typeof data.number }}</pre>
<PlRow>
<PlCheckbox v-model="data.useIncrementButtons">Use increment buttons</PlCheckbox>
<PlCheckbox v-model="data.updateOnEnterOrClickOutside"
>Update on enter or click outside</PlCheckbox
>
<button @click="data.number += 1">Increment number</button>
<button @click="data.number -= 1">Decrement number</button>
<PlCheckbox v-model="data.disableSteps">Disable increment buttons</PlCheckbox>
<button @click="updateNumber(1)">Increment number</button>
<button @click="updateNumber(-1)">Decrement number</button>
<button @click="data.number = NaN">Set NaN</button>
<button @click="data.number = 102">Set 102</button>
<button @click="data.number = '102.2aaaa' as unknown as number">Set '102.2aaaa'</button>
Expand All @@ -31,24 +34,24 @@ const data = reactive({
:min-value="10"
:max-value="100"
label="PlNumberField (min: 10, max: 100)"
:update-on-enter-or-click-outside="data.updateOnEnterOrClickOutside"
:use-increment-buttons="data.useIncrementButtons"
clearable
:disable-steps="data.disableSteps"
/>
<PlNumberField
v-model="data.number"
:min-value="-5"
:max-value="200"
label="PlNumberField (min: -5, max: 200)"
:update-on-enter-or-click-outside="data.updateOnEnterOrClickOutside"
:use-increment-buttons="data.useIncrementButtons"
:clearable="() => undefined"
:disable-steps="data.disableSteps"
/>
<PlNumberField
v-model="data.number"
:max-value="100"
label="PlNumberField (max: 100 and validate is even)"
:validate="(v) => (v % 2 === 0 ? undefined : 'Value must be even')"
:update-on-enter-or-click-outside="data.updateOnEnterOrClickOutside"
:use-increment-buttons="data.useIncrementButtons"
:clearable="() => 33"
:disable-steps="data.disableSteps"
/>
</PlContainer>
</PlRow>
Expand Down
10 changes: 5 additions & 5 deletions etc/blocks/ui-examples/ui/src/pages/PlTextFieldPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ const data = reactive({
optionalNum: "" as string,
});

function numberRule(v: string): boolean | string {
if (v === "") return true;
function numberRule(v: string): string | undefined {
if (v === "") return undefined;
const parsed = Number(v);
if (!Number.isFinite(parsed)) return "Not a number";
return true;
return undefined;
}
</script>

Expand Down Expand Up @@ -42,13 +42,13 @@ function numberRule(v: string): boolean | string {
/>

<div>Number (string) + clearable</div>
<PlTextField v-model="data.num" placeholder="Number" :rules="[numberRule]" clearable />
<PlTextField v-model="data.num" placeholder="Number" :validate="numberRule" clearable />

<div>Optional number (string)</div>
<PlTextField
v-model="data.optionalNum"
placeholder="Number"
:rules="[numberRule]"
:validate="numberRule"
clearable
/>

Expand Down
94 changes: 52 additions & 42 deletions lib/ui/uikit/src/components/PlNumberField/PlNumberField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,19 @@ export default {
};
</script>

<script setup lang="ts">
<script setup lang="ts" generic="V extends undefined | number, C extends V">
import "./pl-number-field.scss";
import DoubleContour from "../../utils/DoubleContour.vue";
import { useLabelNotch } from "../../utils/useLabelNotch";
import { computed, ref, useSlots, watch } from "vue";
import { PlTooltip } from "../PlTooltip";
import { parseNumber } from "./parseNumber";
import { PlIcon16 } from "../PlIcon16";
import { parseNumber, normalizeNumberString } from "./parseNumber";

const modelValue = defineModel<V>({ required: true });

const props = withDefaults(
defineProps<{
/** Input is disabled if true */
disabled?: boolean;
/** Label on the top border of the field, empty by default */
label?: string;
/** Input placeholder, empty by default */
Expand All @@ -40,14 +41,16 @@ const props = withDefaults(
minValue?: number;
/** If defined - show an error if value is higher */
maxValue?: number;
/** If false - remove buttons on the right */
useIncrementButtons?: boolean;
/** If true - changes do not apply immediately, they apply only by removing focus from the input (by click enter or by click outside) */
updateOnEnterOrClickOutside?: boolean;
/** Input is disabled if true */
disabled?: boolean;
/** If true - remove buttons on the right */
disableSteps?: boolean;
/** Error message that shows always when it's provided, without other checks */
errorMessage?: string;
/** Additional validity check for input value that must return an error text if failed */
validate?: (v: number) => string | undefined;
/** If `true`, shows a clear button that resets value to `undefined`. If a function, calls it to get the reset value. */
clearable?: boolean | (() => C);
/** Makes some of corners not rounded */
groupPosition?:
| "top"
Expand All @@ -66,16 +69,14 @@ const props = withDefaults(
placeholder: undefined,
minValue: undefined,
maxValue: undefined,
useIncrementButtons: true,
updateOnEnter: false,
errorMessage: undefined,
validate: undefined,
clearable: false,
groupPosition: undefined,
disableSteps: false,
validate: undefined,
errorMessage: undefined,
},
);

const modelValue = defineModel<number | undefined>({ required: true });

const slots = useSlots();

const rootRef = ref<HTMLElement>();
Expand Down Expand Up @@ -109,19 +110,30 @@ const inputValue = computed({

cachedValue.value = r.cleanInput;

if (r.error || props.updateOnEnterOrClickOutside) {
if (r.error) {
inputRef.value!.value = r.cleanInput;
} else {
modelValue.value = r.value;
modelValue.value = r.value as V;
}
},
});

const focused = ref(false);
const canShowClearable = computed(
() => props.clearable && modelValue.value !== undefined && !props.disabled,
);

function clear() {
if (typeof props.clearable === "function") {
modelValue.value = props.clearable() as V;
} else {
modelValue.value = undefined as V;
}
resetCachedValue();
}

function applyChanges() {
if (parsedResult.value.error === undefined) {
modelValue.value = parsedResult.value.value;
modelValue.value = parsedResult.value.value as C;
}
}

Expand Down Expand Up @@ -183,7 +195,7 @@ function increment() {
nV =
((parsedValue || 0) * multiplier.value + props.step * multiplier.value) / multiplier.value;
}
modelValue.value = props.maxValue !== undefined ? Math.min(props.maxValue, nV) : nV;
modelValue.value = (props.maxValue !== undefined ? Math.min(props.maxValue, nV) : nV) as V;
}
}

Expand All @@ -200,21 +212,11 @@ function decrement() {
nV =
((parsedValue || 0) * multiplier.value - props.step * multiplier.value) / multiplier.value;
}
modelValue.value = props.minValue !== undefined ? Math.max(props.minValue, nV) : nV;
modelValue.value = (props.minValue !== undefined ? Math.max(props.minValue, nV) : nV) as V;
}
}

function handleKeyPress(e: { code: string; preventDefault(): void }) {
if (props.updateOnEnterOrClickOutside) {
if (e.code === "Escape") {
inputValue.value = modelToString(modelValue.value);
inputRef.value?.blur();
}
if (e.code === "Enter") {
inputRef.value?.blur();
}
}

if (e.code === "Enter") {
inputValue.value = String(modelValue.value); // to make .1 => 0.1, 10.00 => 10, remove leading zeros etc
}
Expand All @@ -223,15 +225,23 @@ function handleKeyPress(e: { code: string; preventDefault(): void }) {
e.preventDefault();
}

if (props.useIncrementButtons && e.code === "ArrowUp") {
if (props.disableSteps !== true && e.code === "ArrowUp") {
increment();
}

if (props.useIncrementButtons && e.code === "ArrowDown") {
if (props.disableSteps !== true && e.code === "ArrowDown") {
decrement();
}
}

function handlePaste(e: ClipboardEvent) {
const pasted = e.clipboardData?.getData("text");
if (pasted) {
e.preventDefault();
inputValue.value = normalizeNumberString(pasted);
}
}

// https://stackoverflow.com/questions/880512/prevent-text-selection-after-double-click#:~:text=If%20you%20encounter%20a%20situation,none%3B%20to%20the%20summary%20element.
// this prevents selecting of more than input content in some cases,
// but also disable selecting input content by double-click (useful feature)
Expand All @@ -251,10 +261,7 @@ const onMousedown = (ev: MouseEvent) => {
>
<div class="pl-number-field__main-wrapper d-flex">
<DoubleContour class="pl-number-field__contour" :group-position="groupPosition" />
<div
class="pl-number-field__wrapper flex-grow d-flex flex-align-center"
:class="{ withoutArrows: !useIncrementButtons }"
>
<div class="pl-number-field__wrapper flex-grow d-flex flex-align-center">
<label v-if="label" class="text-description">
{{ label }}
<PlTooltip v-if="slots.tooltip" class="info" position="top">
Expand All @@ -269,15 +276,18 @@ const onMousedown = (ev: MouseEvent) => {
:disabled="disabled"
:placeholder="placeholder"
class="text-s flex-grow"
@focusin="focused = true"
@focusout="
focused = false;
applyChanges();
"
@focusout="applyChanges"
@paste="handlePaste"
/>
<PlIcon16
v-if="canShowClearable"
class="pl-number-field__clearable"
name="delete-clear"
@click.stop="clear"
/>
</div>
<div
v-if="useIncrementButtons"
v-if="!props.disableSteps"
class="pl-number-field__icons d-flex-column"
@mousedown="onMousedown"
>
Expand Down
Loading
Loading