Skip to content

Commit

Permalink
Merge pull request #10 from twm/width-input
Browse files Browse the repository at this point in the history
Improved fractional input
  • Loading branch information
twm authored Oct 30, 2024
2 parents da38541 + 5961406 commit e26f3e6
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 64 deletions.
7 changes: 3 additions & 4 deletions src/lib/Form.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
} from "$lib/stores"
import { frac } from "$lib/frac"
import FracInput from "$lib/FracInput.svelte"
import FracRange from "$lib/FracRange.svelte"
</script>

<h2>Inputs</h2>
Expand All @@ -27,7 +26,7 @@

<label for="frame-width"> Frame width </label>

<FracRange
<FracInput
id="frame-width"
bind:value={$frameWidth}
max={$frameWidthMax}
Expand All @@ -36,7 +35,7 @@
required
/>
<label for="rabbet-width"> Rabbet width </label>
<FracRange
<FracInput
id="rabbet-width"
bind:value={$rabbetWidth}
max={$rabbetWidthMax}
Expand All @@ -57,7 +56,7 @@

<div class="outputs">
<label for="linear-stock">Linear stock </label>
<output id="linear-stock">{frac($stockLength)}"</output>
<output id="linear-stock">{frac($stockLength)}</output>
</div>

<style>
Expand Down
45 changes: 43 additions & 2 deletions src/lib/FracInput.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@
export let value: number
export let min: number | null = null
export let max: number | null = null
export let step: number | null = null // TODO: Add step buttons
export let required: boolean = false
let focused: boolean = false
let input: HTMLInputElement
let rawValue = frac(value)
let displayValue = frac(value) + '"'
let displayValue = frac(value)
$: {
// Round to the nearest 1⁄32nd to match the masked value.
Expand All @@ -41,7 +42,7 @@
if (input) {
if (validate(input, v, min, max)) {
value = v
displayValue = frac(v) + '"'
displayValue = frac(v)
} else {
displayValue = rawValue
}
Expand All @@ -66,9 +67,29 @@
focused = false
}}
/>
{#if step !== null}
<button
disabled={min == null || value - step < min}
on:click={() => {
value -= step
rawValue = frac(value)
}}>-{frac(step)}</button
>
<button
disabled={max == null || value + step > max}
on:click={() => {
value += step
rawValue = frac(value)
}}>+{frac(step)}</button
>
{/if}
</span>

<style>
.range {
display: flex;
gap: 0.2rem;
}
input {
box-sizing: border-box;
width: 100%;
Expand All @@ -82,4 +103,24 @@
input:invalid {
border-bottom-color: var(--invalid-color);
}
button {
box-sizing: border-box;
padding: 0 0.2rem;
line-height: 1;
border: 1px outset black; /* so pretty ✨ */
border-radius: 3px;
background: white;
}
button:active {
border-style: inset; /* even prettier ✨ */
}
button:disabled {
opacity: 0.5;
}
@media print {
button {
display: none;
}
}
</style>
34 changes: 0 additions & 34 deletions src/lib/FracRange.svelte

This file was deleted.

2 changes: 1 addition & 1 deletion src/lib/PartList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
{#each $parts as part}
<tr>
<td>{part.count}</td>
<td>{frac(part.width)}" × {frac(part.length)}"</td>
<td>{frac(part.width)} × {frac(part.length)}</td>
<td>{part.material.name}</td>
</tr>
{/each}
Expand Down
40 changes: 27 additions & 13 deletions src/lib/frac.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ import { frac, parseFrac } from "$lib/frac"

describe("frac renders numbers as mixed fractions", () => {
const cases: [number, string][] = [
[-1 - 3 / 8, "-1\u20093⁄8"],
[-1, "-1"],
[0, "0"],
[1, "1"],
[1.5, "1\u20091⁄2"],
[12.25, "12\u20091⁄4"],
[-6.125, "-6\u20091⁄8"],
[1 / 4, "1⁄4"],
[7 / 8, "7⁄8"],
[15 / 16, "15⁄16"],
[1 + 17 / 32, "1\u200917⁄32"],
[23 / 32, "23⁄32"],
[3 + 61.5 / 64, "3\u200931⁄32"],
[-1 - 3 / 8, '-1\u20093⁄8"'],
[-1, '-1"'],
[0, '0"'],
[1, '1"'],
[1.5, '1\u20091⁄2"'],
[12.25, '12\u20091⁄4"'],
[-6.125, '-6\u20091⁄8"'],
[1 / 4, '1⁄4"'],
[7 / 8, '7⁄8"'],
[15 / 16, '15⁄16"'],
[1 + 17 / 32, '1\u200917⁄32"'],
[23 / 32, '23⁄32"'],
[3 + 61.5 / 64, '3\u200931⁄32"'],
]
test.for(cases)("renders %d as %s", ([n, s]) => {
expect(frac(n)).toEqual(s)
Expand Down Expand Up @@ -57,6 +57,20 @@ describe("parseFrac converts strings to numbers", () => {
[" 123", NaN],
["123 ", NaN],
["1/2.3", NaN],
// " is allowed following the number
['8"', 8],
['8" 1/2"', 8.5],
['6" + 1/2" - 0.25 + 1/2"', 6.75],
['1"/2', NaN],
// ' following the number multiplies by 12 (feet to inches)
["1'", 12],
["1'2", 14], // No whitespace necessary
["1' 2", 14],
["1' 2\"", 14],
["1' + 2", 14],
["1'+2\"", 14],
["1/2' +12\"", 18],
["3'4'5'6'1/2'", (3 + 4 + 5 + 6 + 0.5) * 12],
]
test.for(cases)("parses %j as %d", ([s, n]) => {
expect(parseFrac(s)).toEqual(n)
Expand Down
12 changes: 8 additions & 4 deletions src/lib/frac.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Present a number as a mixed fraction.
* Present a number as a mixed fraction, in inches.
*
* The value is rounded to the nearest 1⁄32nd, and the
* denominator is one of 32, 16, 8, or 4, as on a measuring tape.
Expand Down Expand Up @@ -35,7 +35,7 @@ export function frac(n: number): string {
s += `${num}\u2044${denom}`
}

return s
return s + '"'
}

/**
Expand All @@ -44,7 +44,7 @@ export function frac(n: number): string {
export function parseFrac(s: string): number {
const matches = [
...s.matchAll(
/((?:[+-]\s*)?)(\.\d+|\d+(?:\.\d*)?)(?:[/\u2044](\d+))?(\s+|$|(?=[+-]))/dg
/((?:[+-]\s*)?)(\.\d+|\d+(?:\.\d*)?)(?:[/\u2044](\d+))?(["']?)(\s+|$|(?=[+-])|(?<=["'])(?=\d))/dg
),
]
if (matches.length === 0 || matches[0].index !== 0) {
Expand All @@ -58,9 +58,13 @@ export function parseFrac(s: string): number {
let sign = m[1].substring(0, 1)
let v = parseFloat(m[2])
const denom = m[3]
const unit = m[4]
if (denom) {
v /= parseFloat(denom)
}
if (unit === "'") {
v *= 12 // Feet to inches
}
if (!sign) {
sign = lastSign
}
Expand All @@ -73,7 +77,7 @@ export function parseFrac(s: string): number {
}
}

const [lastStart] = matches[matches.length - 1].indices![4]
const [lastStart] = matches[matches.length - 1].indices![5]
if (lastStart !== s.length) {
// Failure to match, or trailing whitespace or other gunk.
return NaN
Expand Down
4 changes: 2 additions & 2 deletions tests/test.ts-snapshots/calculator-layout-1-Desktop-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions tests/test.ts-snapshots/calculator-layout-1-Phone-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions tests/test.ts-snapshots/calculator-layout-1-Tablet-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit e26f3e6

Please sign in to comment.