Textbox step prop expected to be String
hyvyys opened this issue · comments
With this code
<UiTextbox
type="number"
:value="fontSize"
:min="minFontSize"
:max="maxFontSize"
:step="fontSizeStep"
@input="v => $emit('update:fontSize', parseInt(v))"
/>
I get Invalid prop: type check failed for prop "step". Expected String with value "1", got Number with value 1.
I believe the expected prop type should be Number.
On a sidenote, it would be worth having a separate UiNumber component. I'm kind of in the beginning of creating one by extending UiTextbox, I'll see if it goes anywhere close to a feasible solution.
Obviously I can do
:step="`${fontSizeStep}`"
as a workaround.
I've come up with this, the good part is that I inherited the JS and CSS and only overriden a few elements; the bad part is that I had to copy all the HTML to add new elements and modify props, but then I could do away with the <textarea v-else ... >
so it's not that bad.
Expand code
<script>
const Decimal = require("decimal.js")
import UiTextbox from "keen-ui/src/UiTextbox.vue";
import UiIconButton from "keen-ui/src/UiIconButton.vue";
export default {
name: "UiNumber",
components: { UiIconButton },
extends: UiTextbox,
props: {
value: {
type: Number,
default: 1,
},
step: {
type: Number,
default: 1,
},
clickStep: {
type: Number,
default: 1,
},
clickStepFunction: {
type: Function,
default: null,
},
minlength: {
type: Number,
default: -1,
},
},
data() {
return {
displayedText: null, // only set when text is different from value and input is focused
incrementTimeout: null,
};
},
computed: {
stringValue() {
return this.tempValue.toLocaleString();
},
_clickStep() {
return this.clickStepFunction
? this.clickStepFunction(this.value)
: this.clickStep;
},
},
watch: {
min(val) {
if (this.value < val) this.updateValue(val);
},
max(val) {
if (this.value > val) this.updateValue(val);
},
step() {
if (this.roundedToStep() !== this.value)
this.updateValue(this.roundedToStep());
},
},
methods: {
updateValue(stringOrNumber) {
let stringValue = String(stringOrNumber);
let value = Number(stringValue.replace(",", "."));
if (!isNaN(value)
// prevent erasing decimal point when user wants to change the fraction part
&& !/[.,]$/.test(stringValue)
) {
const corrected = this.correctValue(value);
if (Math.abs(value - corrected) < Number.EPSILON) {
this.displayedText = stringValue;
} else {
this.displayedText = null;
}
this.$emit("input", corrected);
}
},
correctValue(value) {
if (typeof this.min == "number") {
value = Math.max(this.min, value);
}
if (typeof this.max == "number") {
value = Math.min(value, this.max);
}
value = this.roundedToStep(value);
return value;
},
onBlur2(e) {
this.onBlur(e);
this.displayedText = null;
},
roundedToStep(value = this.value) {
return new Decimal(value).toNearest(this.step).toNumber();
},
roundedToClickStep(value = this.value) {
return new Decimal(value).toNearest(this._clickStep).toNumber();
},
getSteps(iteration) {
return iteration < 2
? 1
: Math.ceil((0.1 * this.value) / this._clickStep);
},
getDelay(iteration) {
return iteration < 2 ? 200 : 100;
},
increment(by) {
this.updateValue(this.roundedToClickStep() + by * this._clickStep);
},
decrement(by) {
this.updateValue(this.roundedToClickStep() - by * this._clickStep);
},
startIncrement(e, iteration = 1) {
const steps = this.getSteps(iteration);
this.increment(steps);
this.incrementTimeout = setTimeout(
() => this.startIncrement(e, iteration + 1),
this.getDelay(iteration)
);
},
startDecrement(e, iteration = 1) {
const steps = this.getSteps(iteration);
this.decrement(steps);
this.incrementTimeout = setTimeout(
() => this.startDecrement(e, iteration + 1),
this.getDelay(iteration)
);
},
endIncrementDecrement() {
clearTimeout(this.incrementTimeout);
},
},
};
</script>
<style lang="scss" scoped>
@import "keen-ui/src/styles/util.scss";
@import "keen-ui/src/styles/variables.scss";
$button-height: 0.5 * $ui-input-height - 0.1rem;
$button-width: $button-height + 0.25rem;
.ui-textbox__input-wrapper {
display: flex;
position: relative;
}
.ui-textbox__input {
box-sizing: border-box;
padding-right: $button-width + 0.25rem;
text-align: right;
}
.ui-number-buttons {
opacity: 0.54;
transition: opacity 0.3s;
position: absolute;
top: 0.1em;
right: 0;
display: flex;
flex-direction: column;
.ui-number__button {
height: $button-height;
width: $button-width;
min-width: 0;
padding: 0;
border-radius: 2px;
position: relative;
.ui-icon-button__icon {
position: absolute;
top: 50%;
transform: translateY(-50%);
svg {
height: 0.75em;
transform: scale(1.5);
}
}
}
}
.ui-number:hover {
.ui-number-buttons {
opacity: 1;
transition: opacity 0.3s;
}
}
</style>
<template>
<div class="ui-textbox ui-number" :class="classes">
<div v-if="icon || $slots.icon" class="ui-textbox__icon-wrapper">
<slot name="icon">
<ui-icon :icon="icon"></ui-icon>
</slot>
</div>
<div class="ui-textbox__content">
<label class="ui-textbox__label">
<div class="ui-textbox__input-wrapper">
<input
ref="input"
v-autofocus="autofocus"
class="ui-textbox__input"
:autocomplete="autocomplete ? autocomplete : null"
:disabled="disabled"
:max="maxValue"
:maxlength="enforceMaxlength ? maxlength : null"
:minlength="minlength"
:min="minValue"
:name="name"
:number="type === 'number' ? true : null"
:placeholder="hasFloatingLabel ? null : placeholder"
:readonly="readonly"
:required="required"
:step="stepValue"
:tabindex="tabindex"
:type="type"
:value="displayedText != undefined ? displayedText : value"
@blur="onBlur2"
@focus="onFocus"
@change="updateValue($event.target.value)"
@input="updateValue($event.target.value)"
@keydown.enter="onKeydownEnter"
@keydown="onKeydown"
/>
<div class="ui-number-buttons">
<UiIconButton
class="ui-number__button ui-select__dropdown-button"
@mousedown.native="startIncrement"
@mouseleave.native="endIncrementDecrement"
@mouseup.native="endIncrementDecrement"
>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path
transform="translate(0 24) scale(1 -1) translate(0 -1)"
d="M6.984 9.984h10.03L12 15z"
/>
</svg>
</UiIconButton>
<UiIconButton
class="ui-number__button ui-select__dropdown-button"
@mousedown.native="startDecrement"
@mouseleave.native="endIncrementDecrement"
@mouseup.native="endIncrementDecrement"
>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path transform="translate(0 -1)" d="M6.984 9.984h10.03L12 15z" />
</svg>
</UiIconButton>
</div>
</div>
<div v-if="label || $slots.default" class="ui-textbox__label-text" :class="labelClasses">
<slot>{{ label }}</slot>
</div>
</label>
<div v-if="hasFeedback || maxlength" class="ui-textbox__feedback">
<div v-if="showError" class="ui-textbox__feedback-text">
<slot name="error">{{ error }}</slot>
</div>
<div v-else-if="showHelp" class="ui-textbox__feedback-text">
<slot name="help">{{ help }}</slot>
</div>
<div v-if="maxlength" class="ui-textbox__counter">{{ valueLength + "/" + maxlength }}</div>
</div>
</div>
</div>
</template>
Demo (video): https://recordit.co/nCOScALEEz
I think the type was made a string to match the way you would set it using HTML, but it should also support numbers too. 👍
Yeah, can be done like so:
Keen-UI/src/UiAutocomplete.vue
Line 108 in c2cd4bf