Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Improved fractional input #10

Merged
merged 10 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all 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: 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.
Loading