parent
3c60d33a6c
commit
4dc09c2897
@ -0,0 +1,41 @@
|
|||||||
|
// NumberField — eidos compound API, disciplined option C.
|
||||||
|
//
|
||||||
|
// import { NumberField } from '$uix/eidos/components/number-field';
|
||||||
|
//
|
||||||
|
// <NumberField bind:value min={0} max={10}>
|
||||||
|
// <NumberField.DecrementTrigger>-</NumberField.DecrementTrigger>
|
||||||
|
// <NumberField.Input />
|
||||||
|
// <NumberField.IncrementTrigger>+</NumberField.IncrementTrigger>
|
||||||
|
// </NumberField>
|
||||||
|
import NumberFieldRoot from './number-field.svelte';
|
||||||
|
import Input from './number-field-input.svelte';
|
||||||
|
import IncrementTrigger from './number-field-increment-trigger.svelte';
|
||||||
|
import DecrementTrigger from './number-field-decrement-trigger.svelte';
|
||||||
|
import Scrubber from './number-field-scrubber.svelte';
|
||||||
|
|
||||||
|
type NumberFieldNamespace = typeof NumberFieldRoot & {
|
||||||
|
Input: typeof Input;
|
||||||
|
IncrementTrigger: typeof IncrementTrigger;
|
||||||
|
DecrementTrigger: typeof DecrementTrigger;
|
||||||
|
Scrubber: typeof Scrubber;
|
||||||
|
};
|
||||||
|
|
||||||
|
const NumberField = NumberFieldRoot as NumberFieldNamespace;
|
||||||
|
NumberField.Input = Input;
|
||||||
|
NumberField.IncrementTrigger = IncrementTrigger;
|
||||||
|
NumberField.DecrementTrigger = DecrementTrigger;
|
||||||
|
NumberField.Scrubber = Scrubber;
|
||||||
|
|
||||||
|
export { NumberField };
|
||||||
|
|
||||||
|
export default NumberField;
|
||||||
|
|
||||||
|
export type {
|
||||||
|
NumberFieldFormatOptions,
|
||||||
|
NumberFieldProps,
|
||||||
|
NumberFieldInputProps as InputProps,
|
||||||
|
NumberFieldIncrementTriggerProps as IncrementTriggerProps,
|
||||||
|
NumberFieldDecrementTriggerProps as DecrementTriggerProps,
|
||||||
|
NumberFieldScrubberProps as ScrubberProps,
|
||||||
|
NumberFieldSize
|
||||||
|
} from './types';
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { DecrementTrigger as SomaNumberFieldDecrementTrigger } from '$soma/components/number-field';
|
||||||
|
import type { NumberFieldDecrementTriggerProps } from './types';
|
||||||
|
|
||||||
|
let { children, ...rest }: NumberFieldDecrementTriggerProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SomaNumberFieldDecrementTrigger {...rest}>
|
||||||
|
{@render children?.()}
|
||||||
|
</SomaNumberFieldDecrementTrigger>
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { IncrementTrigger as SomaNumberFieldIncrementTrigger } from '$soma/components/number-field';
|
||||||
|
import type { NumberFieldIncrementTriggerProps } from './types';
|
||||||
|
|
||||||
|
let { children, ...rest }: NumberFieldIncrementTriggerProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SomaNumberFieldIncrementTrigger {...rest}>
|
||||||
|
{@render children?.()}
|
||||||
|
</SomaNumberFieldIncrementTrigger>
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Input as SomaNumberFieldInput } from '$soma/components/number-field';
|
||||||
|
import type { NumberFieldInputProps } from './types';
|
||||||
|
|
||||||
|
let { children, ...rest }: NumberFieldInputProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SomaNumberFieldInput {...rest}>
|
||||||
|
{@render children?.()}
|
||||||
|
</SomaNumberFieldInput>
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Scrubber as SomaNumberFieldScrubber } from '$soma/components/number-field';
|
||||||
|
import type { NumberFieldScrubberProps } from './types';
|
||||||
|
|
||||||
|
let { children, ...rest }: NumberFieldScrubberProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SomaNumberFieldScrubber {...rest}>
|
||||||
|
{@render children?.()}
|
||||||
|
</SomaNumberFieldScrubber>
|
||||||
@ -0,0 +1,134 @@
|
|||||||
|
[data-number-field] {
|
||||||
|
--_number-field-height: var(--number-field-height-md);
|
||||||
|
--_number-field-px: var(--number-field-px-md);
|
||||||
|
--_number-field-gap: var(--number-field-gap-md);
|
||||||
|
--_number-field-font-size: var(--number-field-font-size-md);
|
||||||
|
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--_number-field-gap);
|
||||||
|
inline-size: 100%;
|
||||||
|
min-inline-size: 0;
|
||||||
|
block-size: var(--_number-field-height);
|
||||||
|
padding-inline: var(--_number-field-px);
|
||||||
|
border: var(--number-field-border-width) solid var(--number-field-border);
|
||||||
|
border-radius: var(--number-field-radius);
|
||||||
|
background: var(--number-field-bg);
|
||||||
|
color: var(--number-field-color);
|
||||||
|
font-family: var(--number-field-font-family);
|
||||||
|
font-size: var(--_number-field-font-size);
|
||||||
|
line-height: var(--number-field-line-height);
|
||||||
|
transition:
|
||||||
|
background var(--number-field-transition-duration) var(--number-field-transition-ease),
|
||||||
|
border-color var(--number-field-transition-duration) var(--number-field-transition-ease),
|
||||||
|
box-shadow var(--number-field-transition-duration) var(--number-field-transition-ease);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-number-field][data-size='sm'] {
|
||||||
|
--_number-field-height: var(--number-field-height-sm);
|
||||||
|
--_number-field-px: var(--number-field-px-sm);
|
||||||
|
--_number-field-gap: var(--number-field-gap-sm);
|
||||||
|
--_number-field-font-size: var(--number-field-font-size-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-number-field][data-size='lg'] {
|
||||||
|
--_number-field-height: var(--number-field-height-lg);
|
||||||
|
--_number-field-px: var(--number-field-px-lg);
|
||||||
|
--_number-field-gap: var(--number-field-gap-lg);
|
||||||
|
--_number-field-font-size: var(--number-field-font-size-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-number-field][data-focused],
|
||||||
|
[data-number-field][data-scrubbing] {
|
||||||
|
border-color: var(--number-field-border-focus);
|
||||||
|
box-shadow: var(--number-field-focus-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-number-field][data-invalid] {
|
||||||
|
border-color: var(--number-field-border-invalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-number-field][data-disabled] {
|
||||||
|
opacity: var(--number-field-disabled-opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-number-field-input] {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-inline-size: 0;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: inherit;
|
||||||
|
font: inherit;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-number-field-input][data-disabled] {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-number-field-increment-trigger],
|
||||||
|
[data-number-field-decrement-trigger],
|
||||||
|
[data-number-field-scrubber] {
|
||||||
|
display: inline-flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
inline-size: var(--number-field-control-size);
|
||||||
|
block-size: var(--number-field-control-size);
|
||||||
|
border: var(--number-field-control-border-width) solid var(--number-field-control-border);
|
||||||
|
border-radius: var(--number-field-control-radius);
|
||||||
|
background: var(--number-field-control-bg);
|
||||||
|
color: var(--number-field-control-color);
|
||||||
|
font: inherit;
|
||||||
|
outline: none;
|
||||||
|
transition:
|
||||||
|
background var(--number-field-transition-duration) var(--number-field-transition-ease),
|
||||||
|
border-color var(--number-field-transition-duration) var(--number-field-transition-ease),
|
||||||
|
color var(--number-field-transition-duration) var(--number-field-transition-ease),
|
||||||
|
box-shadow var(--number-field-transition-duration) var(--number-field-transition-ease);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-number-field-increment-trigger],
|
||||||
|
[data-number-field-decrement-trigger] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-number-field-scrubber] {
|
||||||
|
cursor: ew-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-number-field-increment-trigger]:hover:not([data-disabled]),
|
||||||
|
[data-number-field-decrement-trigger]:hover:not([data-disabled]),
|
||||||
|
[data-number-field-scrubber]:hover:not([data-disabled]) {
|
||||||
|
border-color: var(--number-field-control-border-hover);
|
||||||
|
background: var(--number-field-control-bg-hover);
|
||||||
|
color: var(--number-field-control-color-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-number-field-increment-trigger]:focus-visible,
|
||||||
|
[data-number-field-decrement-trigger]:focus-visible,
|
||||||
|
[data-number-field-scrubber]:focus-visible {
|
||||||
|
box-shadow: var(--number-field-control-focus-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-number-field-increment-trigger][data-disabled],
|
||||||
|
[data-number-field-decrement-trigger][data-disabled],
|
||||||
|
[data-number-field-scrubber][data-disabled] {
|
||||||
|
cursor: default;
|
||||||
|
opacity: var(--number-field-control-disabled-opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-number-field-scrubber][data-scrubbing] {
|
||||||
|
border-color: var(--number-field-control-border-active);
|
||||||
|
background: var(--number-field-control-bg-active);
|
||||||
|
color: var(--number-field-control-color-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
[data-number-field],
|
||||||
|
[data-number-field-increment-trigger],
|
||||||
|
[data-number-field-decrement-trigger],
|
||||||
|
[data-number-field-scrubber] {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
/**
|
||||||
|
* Eidos `<NumberField>` — visual wrapper over Soma's spinbutton primitive.
|
||||||
|
* Soma owns parsing, formatting, min/max, scrub and keyboard behavior.
|
||||||
|
*/
|
||||||
|
import { ActiveEidos } from '$uix/eidos';
|
||||||
|
import { Provider as SomaNumberFieldProvider } from '$soma/components/number-field';
|
||||||
|
import type { NumberFieldProps } from './types';
|
||||||
|
|
||||||
|
let { size = 'md', value = $bindable(), children, ...rest }: NumberFieldProps = $props();
|
||||||
|
|
||||||
|
const eidos = ActiveEidos.require();
|
||||||
|
const resolvedSize = $derived(eidos.resolve(size, 'md'));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SomaNumberFieldProvider {...rest} bind:value data-size={resolvedSize}>
|
||||||
|
{@render children?.()}
|
||||||
|
</SomaNumberFieldProvider>
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
import type {
|
||||||
|
DecrementTriggerProps as SomaNumberFieldDecrementTriggerProps,
|
||||||
|
IncrementTriggerProps as SomaNumberFieldIncrementTriggerProps,
|
||||||
|
InputProps as SomaNumberFieldInputProps,
|
||||||
|
NumberFieldFormatOptions,
|
||||||
|
ProviderProps as SomaNumberFieldProviderProps,
|
||||||
|
ScrubberProps as SomaNumberFieldScrubberProps
|
||||||
|
} from '$soma/components/number-field';
|
||||||
|
import type { ResponsiveProp, Size } from '$uix/eidos/lib/types';
|
||||||
|
|
||||||
|
export type NumberFieldSize = Extract<Size, 'sm' | 'md' | 'lg'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the eidos `<NumberField>`.
|
||||||
|
*
|
||||||
|
* Extends Soma's spinbutton with visual sizing only. Formatting, parsing,
|
||||||
|
* scrubbing, keyboard interaction and field integration are owned by Soma.
|
||||||
|
*/
|
||||||
|
export type NumberFieldProps = SomaNumberFieldProviderProps & {
|
||||||
|
/** Visual size. Accepts a responsive map resolved by ActiveEidos. @default 'md' */
|
||||||
|
size?: ResponsiveProp<NumberFieldSize>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NumberFieldInputProps = SomaNumberFieldInputProps;
|
||||||
|
export type NumberFieldIncrementTriggerProps = SomaNumberFieldIncrementTriggerProps;
|
||||||
|
export type NumberFieldDecrementTriggerProps = SomaNumberFieldDecrementTriggerProps;
|
||||||
|
export type NumberFieldScrubberProps = SomaNumberFieldScrubberProps;
|
||||||
|
export type { NumberFieldFormatOptions };
|
||||||
Loading…
Reference in new issue