mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-12-02 18:12:11 +00:00
small fixes for tools
This commit is contained in:
@@ -12,12 +12,14 @@
|
||||
disabled = false,
|
||||
locale,
|
||||
class: className = '',
|
||||
onchange = () => {},
|
||||
}: {
|
||||
value?: DateValue;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
locale: string;
|
||||
class?: string;
|
||||
onchange?: (date: DateValue | undefined) => void;
|
||||
} = $props();
|
||||
|
||||
const df = new DateFormatter(locale, {
|
||||
@@ -43,6 +45,6 @@
|
||||
{value ? df.format(value.toDate(getLocalTimeZone())) : placeholder}
|
||||
</Popover.Trigger>
|
||||
<Popover.Content bind:ref={contentRef} class="w-auto p-0">
|
||||
<Calendar type="single" captionLayout="dropdown" bind:value />
|
||||
<Calendar type="single" captionLayout="dropdown" bind:value onValueChange={onchange} />
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
|
||||
@@ -1,26 +1,45 @@
|
||||
<script lang="ts">
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
|
||||
export let value: string | number;
|
||||
let {
|
||||
id,
|
||||
value = $bindable(),
|
||||
disabled,
|
||||
oninput = () => {},
|
||||
onchange = () => {},
|
||||
onkeypress = () => {},
|
||||
onfocusin = () => {},
|
||||
class: className,
|
||||
}: {
|
||||
id: string;
|
||||
value: string | number;
|
||||
disabled?: boolean;
|
||||
oninput?: (e: Event) => void;
|
||||
onchange?: (e: Event) => void;
|
||||
onkeypress?: (e: KeyboardEvent) => void;
|
||||
onfocusin?: (e: FocusEvent) => void;
|
||||
class?: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<Input
|
||||
{id}
|
||||
type="text"
|
||||
step={1}
|
||||
bind:value
|
||||
on:input
|
||||
on:change
|
||||
on:keypress
|
||||
on:focusin={() => {
|
||||
{disabled}
|
||||
{oninput}
|
||||
{onchange}
|
||||
{onkeypress}
|
||||
onfocusin={(e) => {
|
||||
let input = document.activeElement;
|
||||
if (input instanceof HTMLInputElement) {
|
||||
input.select();
|
||||
}
|
||||
onfocusin(e);
|
||||
}}
|
||||
on:focusin
|
||||
class="w-[22px] {$$props.class ?? ''}"
|
||||
{...$$restProps}
|
||||
class="w-[22px] {className ?? ''}"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -29,8 +48,11 @@
|
||||
|
||||
div :global(input) {
|
||||
@apply px-0.5;
|
||||
@apply py-0;
|
||||
@apply bg-transparent;
|
||||
@apply text-right;
|
||||
@apply border-none;
|
||||
@apply shadow-none;
|
||||
@apply focus:ring-0;
|
||||
@apply focus:ring-offset-0;
|
||||
@apply focus:outline-none;
|
||||
|
||||
@@ -1,160 +1,177 @@
|
||||
<script lang="ts">
|
||||
import TimeComponentInput from './TimeComponentInput.svelte';
|
||||
import { untrack } from 'svelte';
|
||||
import TimeComponentInput from './TimeComponentInput.svelte';
|
||||
|
||||
export let showHours = true;
|
||||
export let value: number | undefined = undefined;
|
||||
export let disabled: boolean = false;
|
||||
export let onChange = () => {};
|
||||
let {
|
||||
showHours = true,
|
||||
value = $bindable(),
|
||||
disabled = false,
|
||||
onChange = () => {},
|
||||
}: {
|
||||
showHours?: boolean;
|
||||
value?: number;
|
||||
disabled?: boolean;
|
||||
onChange?: () => void;
|
||||
} = $props();
|
||||
|
||||
let hours: string | number = '--';
|
||||
let minutes: string | number = '--';
|
||||
let seconds: string | number = '--';
|
||||
let hours: string | number = $state('--');
|
||||
let minutes: string | number = $state('--');
|
||||
let seconds: string | number = $state('--');
|
||||
|
||||
function maybeParseInt(value: string | number): number {
|
||||
if (value === '--' || value === '') {
|
||||
return 0;
|
||||
}
|
||||
return typeof value === 'string' ? parseInt(value) : value;
|
||||
}
|
||||
function maybeParseInt(value: string | number): number {
|
||||
if (value === '--' || value === '') {
|
||||
return 0;
|
||||
}
|
||||
return typeof value === 'string' ? parseInt(value) : value;
|
||||
}
|
||||
|
||||
function computeValue() {
|
||||
return Math.max(
|
||||
maybeParseInt(hours) * 3600 + maybeParseInt(minutes) * 60 + maybeParseInt(seconds),
|
||||
1
|
||||
);
|
||||
}
|
||||
function computeValue(): number {
|
||||
return Math.max(
|
||||
maybeParseInt(hours) * 3600 + maybeParseInt(minutes) * 60 + maybeParseInt(seconds),
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
function updateValue() {
|
||||
value = computeValue();
|
||||
}
|
||||
$effect(() => {
|
||||
const val = computeValue();
|
||||
untrack(() => {
|
||||
value = val;
|
||||
});
|
||||
});
|
||||
|
||||
$: hours, minutes, seconds, updateValue();
|
||||
$effect(() => {
|
||||
if (value === undefined) {
|
||||
untrack(() => {
|
||||
hours = '--';
|
||||
minutes = '--';
|
||||
seconds = '--';
|
||||
});
|
||||
} else {
|
||||
untrack(() => {
|
||||
if (value != computeValue()) {
|
||||
let rounded = Math.max(Math.round(value), 1);
|
||||
if (showHours) {
|
||||
hours = Math.floor(rounded / 3600);
|
||||
minutes = Math.floor((rounded % 3600) / 60)
|
||||
.toString()
|
||||
.padStart(2, '0');
|
||||
} else {
|
||||
minutes = Math.floor(rounded / 60).toString();
|
||||
}
|
||||
seconds = (rounded % 60).toString().padStart(2, '0');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$: if (value === undefined) {
|
||||
hours = '--';
|
||||
minutes = '--';
|
||||
seconds = '--';
|
||||
} else if (value !== computeValue()) {
|
||||
let rounded = Math.max(Math.round(value), 1);
|
||||
if (showHours) {
|
||||
hours = Math.floor(rounded / 3600);
|
||||
minutes = Math.floor((rounded % 3600) / 60)
|
||||
.toString()
|
||||
.padStart(2, '0');
|
||||
} else {
|
||||
minutes = Math.floor(rounded / 60).toString();
|
||||
}
|
||||
seconds = (rounded % 60).toString().padStart(2, '0');
|
||||
}
|
||||
|
||||
let container: HTMLDivElement;
|
||||
let countKeyPress = 0;
|
||||
function onKeyPress(e) {
|
||||
if (['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'].includes(e.key)) {
|
||||
countKeyPress++;
|
||||
if (countKeyPress === 2) {
|
||||
if (e.target.id === 'hours') {
|
||||
container.querySelector('#minutes')?.focus();
|
||||
} else if (e.target.id === 'minutes') {
|
||||
container.querySelector('#seconds')?.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let container: HTMLDivElement;
|
||||
let countKeyPress = 0;
|
||||
function onKeyPress(e) {
|
||||
if (['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'].includes(e.key)) {
|
||||
countKeyPress++;
|
||||
if (countKeyPress === 2) {
|
||||
if (e.target.id === 'hours') {
|
||||
container.querySelector('#minutes')?.focus();
|
||||
} else if (e.target.id === 'minutes') {
|
||||
container.querySelector('#seconds')?.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={container}
|
||||
class="flex flex-row items-center w-full min-w-fit border rounded-md px-3 focus-within:outline-none focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 {disabled
|
||||
? 'opacity-50 cursor-not-allowed'
|
||||
: ''}"
|
||||
bind:this={container}
|
||||
class="h-9 flex flex-row items-center w-full min-w-fit border rounded-md px-3 focus-within:outline-none focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 {disabled
|
||||
? 'opacity-50 cursor-not-allowed'
|
||||
: ''}"
|
||||
>
|
||||
{#if showHours}
|
||||
<TimeComponentInput
|
||||
id="hours"
|
||||
bind:value={hours}
|
||||
{disabled}
|
||||
class="w-[30px]"
|
||||
on:input={() => {
|
||||
if (typeof hours === 'string') {
|
||||
hours = parseInt(hours);
|
||||
}
|
||||
if (hours >= 0) {
|
||||
} else if (hours < 0) {
|
||||
hours = 0;
|
||||
} else {
|
||||
hours = 0;
|
||||
}
|
||||
onChange();
|
||||
}}
|
||||
on:keypress={onKeyPress}
|
||||
on:focusin={() => {
|
||||
countKeyPress = 0;
|
||||
}}
|
||||
/>
|
||||
<span class="text-sm">:</span>
|
||||
{/if}
|
||||
<TimeComponentInput
|
||||
id="minutes"
|
||||
bind:value={minutes}
|
||||
{disabled}
|
||||
on:input={() => {
|
||||
if (typeof minutes === 'string') {
|
||||
minutes = parseInt(minutes);
|
||||
}
|
||||
if (minutes >= 0 && (minutes <= 59 || !showHours)) {
|
||||
} else if (minutes < 0) {
|
||||
minutes = 0;
|
||||
} else if (showHours && minutes > 59) {
|
||||
minutes = 59;
|
||||
} else {
|
||||
minutes = 0;
|
||||
}
|
||||
minutes = minutes.toString().padStart(showHours ? 2 : 1, '0');
|
||||
onChange();
|
||||
}}
|
||||
on:keypress={onKeyPress}
|
||||
on:focusin={() => {
|
||||
countKeyPress = 0;
|
||||
}}
|
||||
/>
|
||||
<span class="text-sm">:</span>
|
||||
<TimeComponentInput
|
||||
id="seconds"
|
||||
bind:value={seconds}
|
||||
{disabled}
|
||||
on:input={() => {
|
||||
if (typeof seconds === 'string') {
|
||||
seconds = parseInt(seconds);
|
||||
}
|
||||
if (seconds >= 0 && seconds <= 59) {
|
||||
} else if (seconds < 0) {
|
||||
seconds = 0;
|
||||
} else if (seconds > 59) {
|
||||
seconds = 59;
|
||||
} else {
|
||||
seconds = 0;
|
||||
}
|
||||
seconds = seconds.toString().padStart(2, '0');
|
||||
onChange();
|
||||
}}
|
||||
on:keypress={onKeyPress}
|
||||
on:focusin={() => {
|
||||
countKeyPress = 0;
|
||||
}}
|
||||
/>
|
||||
{#if showHours}
|
||||
<TimeComponentInput
|
||||
id="hours"
|
||||
bind:value={hours}
|
||||
{disabled}
|
||||
class="w-[30px]"
|
||||
oninput={() => {
|
||||
if (typeof hours === 'string') {
|
||||
hours = parseInt(hours);
|
||||
}
|
||||
if (hours >= 0) {
|
||||
} else if (hours < 0) {
|
||||
hours = 0;
|
||||
} else {
|
||||
hours = 0;
|
||||
}
|
||||
onChange();
|
||||
}}
|
||||
onkeypress={onKeyPress}
|
||||
onfocusin={() => {
|
||||
countKeyPress = 0;
|
||||
}}
|
||||
/>
|
||||
<span class="text-sm">:</span>
|
||||
{/if}
|
||||
<TimeComponentInput
|
||||
id="minutes"
|
||||
bind:value={minutes}
|
||||
{disabled}
|
||||
oninput={() => {
|
||||
if (typeof minutes === 'string') {
|
||||
minutes = parseInt(minutes);
|
||||
}
|
||||
if (minutes >= 0 && (minutes <= 59 || !showHours)) {
|
||||
} else if (minutes < 0) {
|
||||
minutes = 0;
|
||||
} else if (showHours && minutes > 59) {
|
||||
minutes = 59;
|
||||
} else {
|
||||
minutes = 0;
|
||||
}
|
||||
minutes = minutes.toString().padStart(showHours ? 2 : 1, '0');
|
||||
onChange();
|
||||
}}
|
||||
onkeypress={onKeyPress}
|
||||
onfocusin={() => {
|
||||
countKeyPress = 0;
|
||||
}}
|
||||
/>
|
||||
<span class="text-sm">:</span>
|
||||
<TimeComponentInput
|
||||
id="seconds"
|
||||
bind:value={seconds}
|
||||
{disabled}
|
||||
oninput={() => {
|
||||
if (typeof seconds === 'string') {
|
||||
seconds = parseInt(seconds);
|
||||
}
|
||||
if (seconds >= 0 && seconds <= 59) {
|
||||
} else if (seconds < 0) {
|
||||
seconds = 0;
|
||||
} else if (seconds > 59) {
|
||||
seconds = 59;
|
||||
} else {
|
||||
seconds = 0;
|
||||
}
|
||||
seconds = seconds.toString().padStart(2, '0');
|
||||
onChange();
|
||||
}}
|
||||
onkeypress={onKeyPress}
|
||||
onfocusin={() => {
|
||||
countKeyPress = 0;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div :global(input::-webkit-outer-spin-button) {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
div :global(input::-webkit-inner-spin-button) {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
div :global(input[type='number']) {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
div :global(input::-webkit-outer-spin-button) {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
div :global(input::-webkit-inner-spin-button) {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
div :global(input[type='number']) {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user