mirror of https://github.com/Budibase/budibase.git
8 changed files with 233 additions and 2 deletions
@ -0,0 +1,172 @@ |
|||
<script> |
|||
import "@spectrum-css/textfield/dist/index-vars.css" |
|||
import "@spectrum-css/actionbutton/dist/index-vars.css" |
|||
import "@spectrum-css/stepper/dist/index-vars.css" |
|||
import { createEventDispatcher } from "svelte" |
|||
|
|||
export let value = null |
|||
export let placeholder = null |
|||
export let disabled = false |
|||
export let error = null |
|||
export let id = null |
|||
export let readonly = false |
|||
export let updateOnChange = true |
|||
export let quiet = false |
|||
export let min |
|||
export let max |
|||
export let step |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
let focus = false |
|||
|
|||
// We need to keep the field value bound to a different variable in order |
|||
// to properly handle erroneous values. If we don't do this then it is |
|||
// possible for the field to show stale text which does not represent the |
|||
// real value. The reactive statement is to ensure that external changes to |
|||
// the value prop are reflected. |
|||
let fieldValue = value |
|||
$: fieldValue = value |
|||
|
|||
// Ensure step is always a numeric value defaulting to 1 |
|||
$: step = step == null || isNaN(step) ? 1 : step |
|||
|
|||
const updateValue = value => { |
|||
if (readonly) { |
|||
return |
|||
} |
|||
const float = parseFloat(value) |
|||
value = isNaN(float) ? null : float |
|||
if (value != null) { |
|||
if (min != null && value < min) { |
|||
value = min |
|||
} else if (max != null && value > max) { |
|||
value = max |
|||
} |
|||
} |
|||
dispatch("change", value) |
|||
fieldValue = value |
|||
} |
|||
|
|||
const onFocus = () => { |
|||
if (readonly) { |
|||
return |
|||
} |
|||
focus = true |
|||
} |
|||
|
|||
const onBlur = event => { |
|||
if (readonly) { |
|||
return |
|||
} |
|||
focus = false |
|||
updateValue(event.target.value) |
|||
} |
|||
|
|||
const onInput = event => { |
|||
if (readonly || !updateOnChange) { |
|||
return |
|||
} |
|||
updateValue(event.target.value) |
|||
} |
|||
|
|||
const updateValueOnEnter = event => { |
|||
if (readonly) { |
|||
return |
|||
} |
|||
if (event.key === "Enter") { |
|||
updateValue(event.target.value) |
|||
} |
|||
} |
|||
|
|||
const stepUp = () => { |
|||
if (value == null || isNaN(value)) { |
|||
updateValue(step) |
|||
} else { |
|||
updateValue(value + step) |
|||
} |
|||
} |
|||
|
|||
const stepDown = () => { |
|||
if (value == null || isNaN(value)) { |
|||
updateValue(step) |
|||
} else { |
|||
updateValue(value - step) |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<div |
|||
class="spectrum-Stepper" |
|||
class:spectrum-Stepper--quiet={quiet} |
|||
class:is-invalid={!!error} |
|||
class:is-disabled={disabled} |
|||
class:is-focused={focus} |
|||
> |
|||
{#if error} |
|||
<svg |
|||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon" |
|||
focusable="false" |
|||
aria-hidden="true" |
|||
> |
|||
<use xlink:href="#spectrum-icon-18-Alert" /> |
|||
</svg> |
|||
{/if} |
|||
|
|||
<div class="spectrum-Textfield spectrum-Stepper-textfield"> |
|||
<input |
|||
{disabled} |
|||
{readonly} |
|||
{id} |
|||
bind:value={fieldValue} |
|||
placeholder={placeholder || ""} |
|||
type="number" |
|||
class="spectrum-Textfield-input spectrum-Stepper-input" |
|||
on:click |
|||
on:blur |
|||
on:focus |
|||
on:input |
|||
on:keyup |
|||
on:blur={onBlur} |
|||
on:focus={onFocus} |
|||
on:input={onInput} |
|||
on:keyup={updateValueOnEnter} |
|||
/> |
|||
</div> |
|||
<span class="spectrum-Stepper-buttons"> |
|||
<button |
|||
class="spectrum-ActionButton spectrum-ActionButton--sizeM spectrum-Stepper-stepUp" |
|||
tabindex="-1" |
|||
on:click={stepUp} |
|||
> |
|||
<svg |
|||
class="spectrum-Icon spectrum-UIIcon-ChevronUp75" |
|||
focusable="false" |
|||
aria-hidden="true" |
|||
> |
|||
<use xlink:href="#spectrum-css-icon-Chevron75" /> |
|||
</svg> |
|||
</button> |
|||
<button |
|||
class="spectrum-ActionButton spectrum-ActionButton--sizeM spectrum-Stepper-stepDown" |
|||
tabindex="-1" |
|||
on:click={stepDown} |
|||
> |
|||
<svg |
|||
class="spectrum-Icon spectrum-UIIcon-ChevronDown75" |
|||
focusable="false" |
|||
aria-hidden="true" |
|||
> |
|||
<use xlink:href="#spectrum-css-icon-Chevron75" /> |
|||
</svg> |
|||
</button> |
|||
</span> |
|||
</div> |
|||
|
|||
<style> |
|||
.spectrum-Stepper { |
|||
width: 100%; |
|||
} |
|||
.spectrum-Stepper::before { |
|||
display: none; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,45 @@ |
|||
<script> |
|||
import Field from "./Field.svelte" |
|||
import Stepper from "./Core/Stepper.svelte" |
|||
import { createEventDispatcher } from "svelte" |
|||
|
|||
export let value = null |
|||
export let label = null |
|||
export let labelPosition = "above" |
|||
export let placeholder = null |
|||
export let disabled = false |
|||
export let readonly = false |
|||
export let error = null |
|||
export let updateOnChange = true |
|||
export let quiet = false |
|||
export let min = null |
|||
export let max = null |
|||
export let step = 1 |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
const onChange = e => { |
|||
value = e.detail |
|||
dispatch("change", e.detail) |
|||
} |
|||
</script> |
|||
|
|||
<Field {label} {labelPosition} {error}> |
|||
<Stepper |
|||
{updateOnChange} |
|||
{error} |
|||
{disabled} |
|||
{readonly} |
|||
{value} |
|||
{placeholder} |
|||
{quiet} |
|||
{min} |
|||
{max} |
|||
{step} |
|||
on:change={onChange} |
|||
on:click |
|||
on:input |
|||
on:blur |
|||
on:focus |
|||
on:keyup |
|||
/> |
|||
</Field> |
|||
Loading…
Reference in new issue