Skip to content

Commit

Permalink
v1 (#1)
Browse files Browse the repository at this point in the history
* feat: support numeric variant, default is alphanumeric

- add Pincode `type` prop; default value is "alphanumeric"
- remove forwarded `input` event from PincodeInput

* feat: export reactive complete prop

- add reactive `complete` prop to Pincode
- fix the incorrect value being dispatched in the complete event detail

* fix: focus next input on Tab key
  • Loading branch information
metonym authored Jan 2, 2021
1 parent b80a8f4 commit 3e6b66f
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 44 deletions.
2 changes: 0 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

- Number-only variant
- Add option to `selectTextOnFocus`
- Export reactive `completed` prop

## [0.2.0](https://github.com/metonym/svelte-pincode/releases/tag/v0.2.0) - 2021-01-01

Expand Down
61 changes: 45 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ npm i -D svelte-pincode

Bind to either the `code` or `value` prop.

- **`code`** (`string[]`): Array of input values. An empty string represents an undefined value.
- **`value`** (`string`): The `code` props as a string (i.e., `code.join('')`)
- **`code`** (`string[]`): Array of input values. An empty string represents an undefined value
- **`value`** (`string`): `code` joined as a string

<!-- prettier-ignore-start -->
```svelte
Expand All @@ -46,6 +46,23 @@ Bind to either the `code` or `value` prop.
```
<!-- prettier-ignore-end -->

### Numeric variant

By default, this component accepts alphanumeric values.

Set `type` to `"numeric"` to only allow numbers.

<!-- prettier-ignore-start -->
```svelte
<Pincode type="numeric">
<PincodeInput />
<PincodeInput />
<PincodeInput />
<PincodeInput />
</Pincode>
```
<!-- prettier-ignore-end -->

### Initial values

Define intitial input values by using the `code` prop or `value` prop on `PincodeInput`.
Expand All @@ -67,25 +84,25 @@ Define intitial input values by using the `code` prop or `value` prop on `Pincod
```
<!-- prettier-ignore-end -->

### Completion & error states
### Validating upon completion

This example illustrates how you can validate the code once all inputs have a value.
Actual validation is left to the consumer.

`value` is simply the `code` array joined as a string.
This example illustrates how you can validate the code once all inputs have a value by binding to the `complete` prop.

<!-- prettier-ignore-start -->
```svelte
<script>
const correctCode = "1234";
let inputValue = '';
let complete = false;
$: complete = inputValue.length === correctCode.length;
$: success = complete && inputValue === correctCode;
$: error = complete && !success;
</script>
<Pincode bind:value={inputValue}>
<Pincode bind:complete bind:value={inputValue}>
<PincodeInput />
<PincodeInput />
<PincodeInput />
Expand All @@ -102,6 +119,16 @@ This example illustrates how you can validate the code once all inputs have a va
```
<!-- prettier-ignore-end -->

As an alternative to the `complete` prop, you can listen to the dispatched "complete" event:

```html
<Pincode
on:complete="{(e) => {
console.log(e.detail); // { code: string[]; value: string; }
}}"
/>
```

### Programmatic usage

`code` can be set programmatically.
Expand Down Expand Up @@ -246,13 +273,14 @@ input:not(:last-of-type) {

#### Props

| Prop name | Value |
| :------------------ | :------------------------- |
| code | `string[]` (default: `[]`) |
| value | `string` (default: `""`) |
| focusFirstInput | `() => void` |
| focusNextEmptyInput | `() => void` |
| focusLastInput | `() => void` |
| Prop name | Value |
| :------------------ | :--------------------------------------------------------- |
| code | `string[]` (default: `[]`) |
| value | `string` (default: `""`) |
| type | `"alphanumeric"` or `"numeric"` (defaul: `"alhpanumeric"`) |
| focusFirstInput | `() => void` |
| focusNextEmptyInput | `() => void` |
| focusLastInput | `() => void` |

#### Dispatched Events

Expand All @@ -268,6 +296,8 @@ input:not(:last-of-type) {

### PincodeInput

#### Props

| Prop name | Value |
| :-------- | :--------------------------------------------------------- |
| id | `string` (default: `"input" + Math.random().toString(36)`) |
Expand All @@ -277,12 +307,11 @@ input:not(:last-of-type) {

- on:focus
- on:blur
- on:input
- on:keydown

## TypeScript

To use this component with TypeScript, you will need `svelte` version 3.31 or greater.
`svelte` version 3.31 or greater is required to use this component with TypeScript.

## Changelog

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"svelte-check": "svelte-check"
},
"devDependencies": {
"svelte": "^3.31.0",
"svelte": "^3.31.1",
"svelte-check": "^1.1.24",
"svelte-readme": "^2.1.2"
},
Expand Down
44 changes: 35 additions & 9 deletions src/Pincode.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
export let value = "";
/** @type {"alphanumeric" | "numeric"} */
export let type = "alphanumeric";
/** `true` if all inputs have a value */
export let complete = false;
/** @type {() => void} */
export const focusFirstInput = () => {
ref.querySelector("input").focus();
Expand All @@ -29,23 +35,42 @@
ref.querySelector("input:last-of-type").focus();
};
import { setContext, createEventDispatcher } from "svelte";
import { setContext, createEventDispatcher, tick } from "svelte";
import { writable, derived } from "svelte/store";
const dispatch = createEventDispatcher();
const _ids = writable([]);
const _valuesById = derived(_ids, (_) => {
return _.reduce((a, c) => ({ ...a, [c.id]: c.value }), {});
});
const _type = writable(type);
let ref = null;
function setCode() {
code = $_ids.map((_) => _.value || "");
}
function focusNextInput(idx) {
const inputs = ref.querySelectorAll("input");
if (idx === inputs.length - 1) {
return inputs[idx].blur();
}
const nextInput = inputs[idx + 1];
if (nextInput) nextInput.focus();
}
setContext("Pincode", {
_type,
_valuesById,
focusNextInput: (id) => {
const idx = $_ids.map((_) => _.id).indexOf(id);
focusNextInput(idx);
},
add: (id, value) => {
let _code = [...code];
Expand All @@ -71,25 +96,22 @@
_ids.update((_) => _.filter((_id) => _id.id !== id));
setCode();
},
update: (id, value) => {
update: async (id, input_value) => {
const idx = $_ids.map((_) => _.id).indexOf(id);
_ids.update((_) => {
return _.map((_id, i) => {
if (i === idx) return { ..._id, value };
if (i === idx) return { ..._id, value: input_value };
return _id;
});
});
setCode();
focusNextInput(idx);
const inputs = ref.querySelectorAll("input");
const nextInput = inputs[idx + 1];
await tick();
if (nextInput) nextInput.focus();
if (code.filter(Boolean).length === $_ids.length) {
dispatch("complete", { code, value });
}
if (complete) dispatch("complete", { code, value });
},
clear: (id) => {
const idx = $_ids.map((_) => _.id).indexOf(id);
Expand All @@ -115,6 +137,8 @@
},
});
$: _type.set(type);
$: value = code.join("");
$: if (code) {
Expand All @@ -126,6 +150,8 @@
$: if (code.length === 0) {
_ids.update((_) => _.map((_id) => ({ ..._id, value: "" })));
}
$: complete = code.filter(Boolean).length === $_ids.length;
</script>

<style>
Expand Down
31 changes: 20 additions & 11 deletions src/PincodeInput.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
import { getContext, onMount } from "svelte";
let type;
const ctx = getContext("Pincode");
const unsubscribeType = ctx._type.subscribe((_type) => {
type = _type;
});
let unsubscribe = undefined;
Expand All @@ -20,6 +25,7 @@
return () => {
ctx.remove(id);
unsubscribe();
unsubscribeType();
};
});
</script>
Expand All @@ -46,22 +52,25 @@
<input
bind:this="{ref}"
{...$$restProps}
type="text"
inputmode="numeric"
pattern="[0-9]{1}"
type="{type === 'numeric' ? 'number' : 'text'}"
inputmode="{type === 'numeric' ? 'numeric' : 'text'}"
pattern="{type === 'numeric' ? '[0-9]{1}' : '^[a-zA-Z0-9]$'}"
maxlength="1"
value="{value}"
on:focus
on:blur
on:input
on:input="{(e) => {
ctx.update(id, e.target.value);
}}"
on:keydown
on:keydown="{(e) => {
if (e.key === 'Backspace') {
e.preventDefault();
ctx.clear(id);
on:keydown|preventDefault="{(e) => {
if (e.key === 'Tab') return ctx.focusNextInput(id);

if (e.key === 'Backspace') return ctx.clear(id);

if (type === 'numeric' && /^[0-9]$/.test(e.key)) {
ctx.update(id, e.key);
}

if (type === 'alphanumeric' && /^[a-zA-Z0-9]$/.test(e.key)) {
ctx.update(id, e.key);
}
}}"
/>
11 changes: 11 additions & 0 deletions types/Pincode.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ export interface PincodeProps extends svelte.JSX.HTMLAttributes<HTMLElementTagNa
*/
value?: string;

/**
* @default "alphanumeric"
*/
type?: "alphanumeric" | "numeric";

/**
* `true` if all inputs have a value
* @default false
*/
complete?: boolean;

/**
* @constant
* @default () => { ref.querySelector("input").focus(); }
Expand Down
1 change: 0 additions & 1 deletion types/PincodeInput.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ export default class PincodeInput extends SvelteComponentTyped<
{
focus: WindowEventMap["focus"];
blur: WindowEventMap["blur"];
input: WindowEventMap["input"];
keydown: WindowEventMap["keydown"];
},
{}
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -759,10 +759,10 @@ svelte-readme@^2.1.2:
rollup-plugin-svelte "^7.0.0"
rollup-plugin-terser "^7.0.2"

svelte@^3.31.0:
version "3.31.0"
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.31.0.tgz#13966e5f55b975bc86675469bb2c58dd0e558d97"
integrity sha512-r+n8UJkDqoQm1b+3tA3Lh6mHXKpcfOSOuEuIo5gE2W9wQYi64RYX/qE6CZBDDsP/H4M+N426JwY7XGH4xASvGQ==
svelte@^3.31.1:
version "3.31.1"
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.31.1.tgz#1a1e2a2fe4eeb72be5c2542735e035c49c45be87"
integrity sha512-Q8xVz5U/IFFNjgvVSjdzKJPAX0MFytFwiJo1HAPfGwM7LkHA+BN2q2kL8vKcJwjku7/509MapLov8C9SjogNRg==

terser@^5.0.0:
version "5.5.1"
Expand Down

0 comments on commit 3e6b66f

Please sign in to comment.