Fix type errors after terra reorganization

- Add DatiTimeFormat ('12h'|'24h') and DatiDateOrder aliases to dati_types.ts;
  replace numeric HourCycle throughout dati_engine so actx and tests compile
- Add missing logger field to TerraConfigState.opts
- Fix percent NumberField: use edit-mode formatter on focus/commit/watch.pre
- Fix test pages: Toolbar.Group value type (string), Progress tuple cast

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main
dev 6 months ago
parent ffdbb79234
commit ad1a1f8946

@ -50,3 +50,5 @@ export function createDatiState(
}, },
}; };
} }

@ -1,9 +1,8 @@
import type { DatiInstance, DatiOptions, DatiConfig } from './dati_types.ts'; import type { DatiInstance, DatiOptions, DatiConfig, DatiTimeFormat, DatiDateOrder } from './dati_types.ts';
import type { LocaleResolver } from './comn_types.ts'; import type { LocaleResolver } from './comn_types.ts';
import { resolveLocale, cachedDateTimeFormat } from './comn_engine.ts'; import { resolveLocale, cachedDateTimeFormat } from './comn_engine.ts';
import { DEFAULT_ISO_LOCALE } from '../../ling/lib/consts.ts'; import { DEFAULT_ISO_LOCALE } from '../../ling/lib/consts.ts';
import type { HourCycle } from '@/lib/util/dates'; import type { DateStyleType } from '$lib/util/dates/core/types';
import type { DateOrder, DateStyleType } from '$lib/util/dates/core/types';
// ============================== // ==============================
@ -42,16 +41,16 @@ export function createDati(
defaultLocale: LocaleResolver = DEFAULT_ISO_LOCALE defaultLocale: LocaleResolver = DEFAULT_ISO_LOCALE
): DatiInstance { ): DatiInstance {
let timeFormat: HourCycle = opts.selectedTimeFormat ?? 24; let timeFormat: DatiTimeFormat = opts.selectedTimeFormat ?? '24h';
let dateOrder : DateOrder = opts.selectedDateOrder ?? 'DMY'; let dateOrder : DatiDateOrder = opts.selectedDateOrder ?? 'DMY';
// ── Preferencias ────────────────────────────────────────────────────────── // ── Preferencias ──────────────────────────────────────────────────────────
function getTimeFormat(): HourCycle { return timeFormat; } function getTimeFormat(): DatiTimeFormat { return timeFormat; }
function setTimeFormat(format: HourCycle): void { timeFormat = format; } function setTimeFormat(format: DatiTimeFormat): void { timeFormat = format; }
function getDateOrder(): DateOrder { return dateOrder; } function getDateOrder(): DatiDateOrder { return dateOrder; }
function setDateOrder(order: DateOrder): void { dateOrder = order; } function setDateOrder(order: DatiDateOrder): void { dateOrder = order; }
// ── Format ──────────────────────────────────────────────────────────────── // ── Format ────────────────────────────────────────────────────────────────
@ -61,7 +60,7 @@ export function createDati(
if (dateStyle) intlOpts.dateStyle = dateStyle; if (dateStyle) intlOpts.dateStyle = dateStyle;
if (timeStyle) { if (timeStyle) {
intlOpts.timeStyle = timeStyle; intlOpts.timeStyle = timeStyle;
intlOpts.hour12 = timeFormat === 12; intlOpts.hour12 = timeFormat === '12h';
} }
return intlOpts; return intlOpts;
} }

@ -1,9 +1,6 @@
import type { LocaleResolver } from "./comn_types.ts"; import type { LocaleResolver } from "./comn_types.ts";
import type { HourCycle } from "$lib/util/dates";
import type { DateOrder, DateStyleType } from '$lib/util/dates/core/types'; import type { DateOrder, DateStyleType } from '$lib/util/dates/core/types';
/** /**
* Opciones de `f()`: * Opciones de `f()`:
* - `'short'` / `'medium'` / `'long'` / `'full'` : solo fecha en ese estilo. * - `'short'` / `'medium'` / `'long'` / `'full'` : solo fecha en ese estilo.
@ -13,12 +10,22 @@ export type DatiOptions =
| DateStyleType | DateStyleType
| { date?: DateStyleType; time?: DateStyleType }; | { date?: DateStyleType; time?: DateStyleType };
/**
* Formato de hora usado en el API de dati.
* - `'12h'` → reloj de 12 horas (AM/PM)
* - `'24h'` → reloj de 24 horas
*/
export type DatiTimeFormat = '12h' | '24h';
/** Alias de `DateOrder` para el API de dati. */
export type DatiDateOrder = DateOrder;
/** Configuración inicial de `createDati`. */ /** Configuración inicial de `createDati`. */
export interface DatiConfig { export interface DatiConfig {
/** Formato de horas. Default: `'24h'`. */ /** Formato de horas. Default: `'24h'`. */
selectedTimeFormat?: HourCycle; selectedTimeFormat?: DatiTimeFormat;
/** Orden de los componentes de fecha. Default: `'DMY'`. */ /** Orden de los componentes de fecha. Default: `'DMY'`. */
selectedDateOrder?: DateOrder; selectedDateOrder?: DatiDateOrder;
} }
export interface DatiInstance { export interface DatiInstance {
@ -29,15 +36,15 @@ export interface DatiInstance {
* Usa `Intl.DateTimeFormat.formatRange()` con fallback para navegadores antiguos. * Usa `Intl.DateTimeFormat.formatRange()` con fallback para navegadores antiguos.
*/ */
fRange : (start: Date, end: Date, opts?: DatiOptions, locale?: LocaleResolver) => string; fRange : (start: Date, end: Date, opts?: DatiOptions, locale?: LocaleResolver) => string;
setTimeFormat: (format: HourCycle) => void; setTimeFormat: (format: DatiTimeFormat) => void;
getTimeFormat: () => HourCycle; getTimeFormat: () => DatiTimeFormat;
/** Formato de hora actual. Reactivo en la versión svelte. */ /** Formato de hora actual. Reactivo en la versión svelte. */
readonly timeFormat : HourCycle; readonly timeFormat : DatiTimeFormat;
/** Guarda la preferencia de orden de fecha para uso de la UI. No afecta a `Intl`. */ /** Guarda la preferencia de orden de fecha para uso de la UI. No afecta a `Intl`. */
setDateOrder : (order: DateOrder) => void; setDateOrder : (order: DatiDateOrder) => void;
getDateOrder : () => DateOrder; getDateOrder : () => DatiDateOrder;
/** Orden de fecha actual. Reactivo en la versión svelte. */ /** Orden de fecha actual. Reactivo en la versión svelte. */
readonly dateOrder : DateOrder; readonly dateOrder : DatiDateOrder;
} }

@ -61,10 +61,10 @@
<section class="card"> <section class="card">
<h2>Múltiples variantes</h2> <h2>Múltiples variantes</h2>
{#each [[20, '#6366f1'], [45, '#22c55e'], [70, '#f59e0b'], [100, '#ef4444']] as [val, color]} {#each [[20, '#6366f1'], [45, '#22c55e'], [70, '#f59e0b'], [100, '#ef4444']] as [val, color] (val)}
<div class="variant-row"> <div class="variant-row">
<span class="variant-label">{val}%</span> <span class="variant-label">{val}%</span>
<Progress.Root value={val} max={100} class="track track--sm"> <Progress.Root value={val as number} max={100} class="track track--sm">
<div class="fill" style:width="{val}%" style:background={color}></div> <div class="fill" style:width="{val}%" style:background={color}></div>
</Progress.Root> </Progress.Root>
</div> </div>

@ -1,18 +1,15 @@
<script lang="ts"> <script lang="ts">
import { Toolbar } from '$uix/terra'; import { Toolbar } from '$uix/terra';
let bold = $state(false); let bold = $state<string | undefined>(undefined);
let italic = $state(false);
let underline = $state(false);
let strikethrough = $state(false);
let align = $state('left'); let align = $state('left');
let lists = $state<string[]>([]); let lists = $state<string[]>([]);
const previewStyle = $derived([ const previewStyle = $derived([
bold ? 'font-weight:700' : '', bold === 'bold' ? 'font-weight:700' : '',
italic ? 'font-style:italic' : '', lists.includes('italic') ? 'font-style:italic' : '',
underline ? 'text-decoration:underline' : '', lists.includes('underline') ? 'text-decoration:underline' : '',
strikethrough ? 'text-decoration:line-through' : '', lists.includes('strike') ? 'text-decoration:line-through' : '',
].filter(Boolean).join(';')); ].filter(Boolean).join(';'));
</script> </script>
@ -25,7 +22,7 @@
<!-- Formato de texto: single selection --> <!-- Formato de texto: single selection -->
<Toolbar.Group type="single" bind:value={bold} class="tb-group"> <Toolbar.Group type="single" bind:value={bold} class="tb-group">
<Toolbar.GroupItem value={true} class="tb-btn {bold ? 'tb-btn--on' : ''}"> <Toolbar.GroupItem value="bold" class="tb-btn {bold === 'bold' ? 'tb-btn--on' : ''}">
<strong>B</strong> <strong>B</strong>
</Toolbar.GroupItem> </Toolbar.GroupItem>
</Toolbar.Group> </Toolbar.Group>

@ -0,0 +1,93 @@
import { createTerraContext } from '$terra/utils/context';
import { type Active, type ActiveProps, readableActive } from '$terra/utils';
import type { TerraConfigPropsWithoutChildren } from './types';
type TerraConfigStateProps = ActiveProps<TerraConfigPropsWithoutChildren>;
export const TerraConfigContext = createTerraContext<TerraConfigState>('TerraConfig');
export function getTerraConfig() {
const fallback = new TerraConfigState(null, {});
return TerraConfigContext.getOr(fallback).opts;
}
export function useTerraConfig(opts: TerraConfigStateProps) {
return TerraConfigContext.set(new TerraConfigState(TerraConfigContext.getOr(null), opts));
}
export class TerraConfigState {
readonly opts: Required<TerraConfigStateProps>;
constructor(parent: TerraConfigState | null, opts: TerraConfigStateProps) {
const resolveConfigOption = createConfigResolver(parent, opts);
this.opts = {
defaultPortalTo: resolveConfigOption((config) => config.defaultPortalTo),
defaultLocale: resolveConfigOption((config) => config.defaultLocale),
defaultDir: resolveConfigOption((config) => config.defaultDir),
defaultDateOrder: resolveConfigOption((config) => config.defaultDateOrder),
defaultTimeFormat: resolveConfigOption((config) => config.defaultTimeFormat),
logger: resolveConfigOption((config) => config.logger),
};
}
}
type ConfigOptionGetter<T> = (config: TerraConfigStateProps) => Active<T> | undefined;
type ConfigOptionResolver = <T>(getter: ConfigOptionGetter<T>) => Active<T | undefined>;
function createConfigResolver(
parent: TerraConfigState | null,
currentOpts: TerraConfigStateProps
): ConfigOptionResolver {
return <T>(getter: ConfigOptionGetter<T>) => {
return readableActive(() => {
const value = getter(currentOpts)?.current;
if (value !== undefined) return value;
if (parent === null) return undefined;
return getter(parent.opts)?.current;
});
};
}

@ -3,7 +3,6 @@ import { createTerraContext } from '$terra/utils/context';
import { import {
attachRef, attachRef,
kbd, kbd,
readableActive,
} from '$terra/utils'; } from '$terra/utils';
import type { import type {
Active, Active,
@ -19,8 +18,8 @@ import type {
} from '$terra/utils'; } from '$terra/utils';
import { boolToEmptyStrOrUndef, boolToStr, createTerraAttrs } from '../utils/attrs'; import { boolToEmptyStrOrUndef, boolToStr, createTerraAttrs } from '../utils/attrs';
import { resolveLocaleProp, resolveDirectionProp } from '../config/prop-resolvers'; import { resolveLocaleProp, resolveDirectionProp } from '../config/prop-resolvers';
import { createNumr } from '@/glob/lib'; import { createNumr, type NumrFormat } from '@/lib';
import type { NumrFormat } from '@/glob/lib';
// ─── Attrs ──────────────────────────────────────────────────────────────────── // ─── Attrs ────────────────────────────────────────────────────────────────────
@ -91,9 +90,9 @@ export class NumberFieldRootState {
watch.pre( watch.pre(
() => [opts.value.current, this.#locale.current, opts.formatOptions.current] as const, () => [opts.value.current, this.#locale.current, opts.formatOptions.current] as const,
([value]) => { ([value]) => {
if (!this.#focused) { this.#inputValue = this.#focused
this.#inputValue = this.#formatValue(value); ? this.#formatEditValue(value)
} : this.#formatValue(value);
}, },
); );
} }
@ -114,15 +113,30 @@ export class NumberFieldRootState {
readonly dir = $derived.by(() => this.#dir.current); readonly dir = $derived.by(() => this.#dir.current);
/** /**
* Instancia NumrInstance reactiva. * Formatter de display: usa el formato completo del consumidor (style, currency, percent…).
* Se recrea solo cuando cambian formatOptions o el locale. * Se recrea cuando cambian formatOptions o locale.
* La dirección se gestiona por separado (atributo HTML, no formateo).
*/ */
readonly #numr = $derived.by(() => { readonly #numr = $derived.by(() => {
const fmt: NumrFormat = { ...(this.opts.formatOptions.current ?? {}) }; const fmt: NumrFormat = { ...(this.opts.formatOptions.current ?? {}) };
return createNumr(fmt, this.#locale.current); return createNumr(fmt, this.#locale.current);
}); });
/**
* Formatter de edición: siempre decimal, sin decoración de estilo.
* Permite que el usuario edite "75" en vez de "75%" o "1.234,56 €".
* Para `percent`, el valor de edición es `value * 100`.
*/
readonly #editNumr = $derived.by(() => {
const fmt = this.opts.formatOptions.current ?? {};
// Heredar precisión decimal del formato original; quitar style/currency/unit.
const editFmt: NumrFormat = {
minDecimals: fmt.minDecimals,
maxDecimals: fmt.maxDecimals,
grouping: fmt.grouping,
};
return createNumr(editFmt, this.#locale.current);
});
readonly atMin = $derived.by( readonly atMin = $derived.by(
() => this.#min !== undefined () => this.#min !== undefined
&& this.opts.value.current !== undefined && this.opts.value.current !== undefined
@ -140,15 +154,36 @@ export class NumberFieldRootState {
// ── Format / Parse ─────────────────────────────────────────────────────── // ── Format / Parse ───────────────────────────────────────────────────────
/**
* Para `percent`, el valor interno 0.75 se edita como 75.
* Para el resto, el valor de edición coincide con el interno.
*/
#toEditValue(value: number): number {
return this.opts.formatOptions.current?.style === 'percent' ? value * 100 : value;
}
#fromEditValue(editValue: number): number {
return this.opts.formatOptions.current?.style === 'percent' ? editValue / 100 : editValue;
}
/** Formatea para mostrar al usuario (display): aplica style, currency, %, etc. */
#formatValue(value: number | undefined): string { #formatValue(value: number | undefined): string {
if (value === undefined || Number.isNaN(value)) return ''; if (value === undefined || Number.isNaN(value)) return '';
return this.#numr.f(value); return this.#numr.f(value);
} }
/** Formatea para edición: decimal puro, valor escalado según el estilo. */
#formatEditValue(value: number | undefined): string {
if (value === undefined || Number.isNaN(value)) return '';
return this.#editNumr.f(this.#toEditValue(value));
}
/** Parsea el string de edición y convierte al valor interno. */
#parseValue(raw: string): number | undefined { #parseValue(raw: string): number | undefined {
if (!raw.trim()) return undefined; if (!raw.trim()) return undefined;
const n = this.#numr.parse(raw); const n = this.#editNumr.parse(raw);
return Number.isNaN(n) ? undefined : n; if (Number.isNaN(n)) return undefined;
return this.#fromEditValue(n);
} }
#clamp(value: number): number { #clamp(value: number): number {
@ -174,7 +209,7 @@ export class NumberFieldRootState {
const clamped = this.#clamp(this.#round(value, this.#step)); const clamped = this.#clamp(this.#round(value, this.#step));
this.opts.value.current = clamped; this.opts.value.current = clamped;
this.opts.onValueChange.current?.(clamped); this.opts.onValueChange.current?.(clamped);
this.#inputValue = this.#focused ? this.#inputValue : this.#formatValue(clamped); this.#inputValue = this.#focused ? this.#formatEditValue(clamped) : this.#formatValue(clamped);
} }
// ── Public actions ─────────────────────────────────────────────────────── // ── Public actions ───────────────────────────────────────────────────────
@ -195,9 +230,8 @@ export class NumberFieldRootState {
readonly handleFocus = (_e: TerraFocusEvent<HTMLInputElement>): void => { readonly handleFocus = (_e: TerraFocusEvent<HTMLInputElement>): void => {
this.#focused = true; this.#focused = true;
// Al enfocar, mostrar el número en bruto (sin formato).
const raw = this.opts.value.current; const raw = this.opts.value.current;
this.#inputValue = raw !== undefined ? String(raw) : ''; this.#inputValue = raw !== undefined ? this.#formatEditValue(raw) : '';
}; };
readonly handleBlur = (_e: TerraFocusEvent<HTMLInputElement>): void => { readonly handleBlur = (_e: TerraFocusEvent<HTMLInputElement>): void => {

Loading…
Cancel
Save

Powered by TurnKey Linux.