diff --git a/src/uix/terra/base/config/components/terra-config.svelte b/src/uix/terra/base/config/components/terra-config.svelte index b27d9485b..65d21a89b 100644 --- a/src/uix/terra/base/config/components/terra-config.svelte +++ b/src/uix/terra/base/config/components/terra-config.svelte @@ -1,17 +1,24 @@ - - -{@render children?.()} - + + +{@render children?.()} diff --git a/src/uix/terra/base/config/exports.ts b/src/uix/terra/base/config/exports.ts index 6ac741289..036ed5005 100644 --- a/src/uix/terra/base/config/exports.ts +++ b/src/uix/terra/base/config/exports.ts @@ -1,8 +1,9 @@ -export { default as TerraConfig } from './components/terra-config.svelte'; -export { getTerraConfig, TerraConfigState } from './terra-config.js'; -export type { - TerraConfigProps, - TerraConfigPropsWithoutChildren, - TerraDatePresentationProps, - TerraDateTimePresentationProps, -} from './types.ts'; +export { default as TerraConfig } from './components/terra-config.svelte'; +export { getTerraConfig, TerraConfigState } from './terra-config.js'; +export type { + TerraConfigProps, + TerraConfigPropsWithoutChildren, + TerraPresentationProps, + TerraDatePresentationProps, + TerraDateTimePresentationProps, +} from './types.ts'; diff --git a/src/uix/terra/base/config/prop-resolvers.ts b/src/uix/terra/base/config/prop-resolvers.ts index 59b816e85..738fc56d0 100644 --- a/src/uix/terra/base/config/prop-resolvers.ts +++ b/src/uix/terra/base/config/prop-resolvers.ts @@ -1,72 +1,86 @@ -import { readableActive, type Active, type Getter } from '../../utils'; -import { DEFAULT_DATE_ORDER, DEFAULT_TIME_FORMAT, timeFormatToHourCycle, type HourCycle } from '$terra/adapters/dates'; -import { type TerraConfigState, getTerraConfig } from './terra-config.ts'; - -/** - * Creates a generic prop resolver that follows a standard priority chain: - * 1. The getter's prop value (if defined) - * 2. The config default value (if no getter prop value is defined) - * 3. The fallback value (if no config value found) - */ -function createPropResolver( - configOption: (config: TerraConfigState['opts']) => { current: T | undefined }, - fallback: T -) { - return (getProp: Getter): Active => { - const config = getTerraConfig(); - return readableActive(() => { - // 1. return the prop's value, if provided - const propValue = getProp(); - if (propValue !== undefined) return propValue; - // 2. return the resolved config option value, if available - const option = configOption(config).current; - if (option !== undefined) return option; - // 3. return the fallback if no other value is found - return fallback; - }); - }; -} - -/** - * Resolves a locale value using the prop, the config default, or a fallback. - * - * Default value: `"en"` - */ -export const resolveLocaleProp = createPropResolver((config) => config.defaultLocale, "en"); - -/** - * Resolves a portal's `to` value using the prop, the config default, or a fallback. - * - * Default value: `"body"` - */ -export const resolvePortalToProp = createPropResolver((config) => config.defaultPortalTo, "body"); - - -/** - * Resolves a date order using the prop, the config default, or the library fallback. - */ -export const resolveDateOrderProp = createPropResolver((config) => config.defaultDateOrder, DEFAULT_DATE_ORDER); - -/** - * Resolves an hour cycle using explicit time/date props, config defaults, or the library fallback. - */ -export function resolveHourCycleProp( - getTimeFormat: Getter, - getHourCycle: Getter -): Active { - const config = getTerraConfig(); - return readableActive(() => { - const timeFormat = getTimeFormat(); - if (timeFormat !== undefined) return timeFormatToHourCycle(timeFormat); - - const hourCycle = getHourCycle(); - if (hourCycle !== undefined) return hourCycle; - - const configTimeFormat = config.defaultTimeFormat.current; - if (configTimeFormat !== undefined) return timeFormatToHourCycle(configTimeFormat); - - return timeFormatToHourCycle(DEFAULT_TIME_FORMAT); - }); -} - - +import { readableActive, type Active, type Direction, type Getter } from '$terra/utils'; +import { + DEFAULT_DATE_ORDER, + DEFAULT_TIME_FORMAT, + timeFormatToHourCycle, + type HourCycle, +} from '$terra/adapters/dates'; +import { type TerraConfigState, getTerraConfig } from './terra-config.ts'; + +/** + * Creates a generic prop resolver that follows a standard priority chain: + * 1. The getter's prop value (if defined) + * 2. The config default value (if no getter prop value is defined) + * 3. The fallback value (if no config value found) + */ +function createPropResolver( + configOption: (config: TerraConfigState['opts']) => { current: T | undefined }, + fallback: T +) { + return (getProp: Getter): Active => { + const config = getTerraConfig(); + return readableActive(() => { + const propValue = getProp(); + if (propValue !== undefined) return propValue; + + const option = configOption(config).current; + if (option !== undefined) return option; + + return fallback; + }); + }; +} + +/** + * Resolves a locale value using the prop, the config default, or a fallback. + * + * Default value: `"en"` + */ +export const resolveLocaleProp = createPropResolver((config) => config.defaultLocale, 'en'); + +/** + * Resolves a direction value using the prop, the config default, or a fallback. + * + * Default value: `"ltr"` + */ +export const resolveDirectionProp = createPropResolver( + (config) => config.defaultDir, + 'ltr' as Direction +); + +/** + * Resolves a portal's `to` value using the prop, the config default, or a fallback. + * + * Default value: `"body"` + */ +export const resolvePortalToProp = createPropResolver((config) => config.defaultPortalTo, 'body'); + +/** + * Resolves a date order using the prop, the config default, or the library fallback. + */ +export const resolveDateOrderProp = createPropResolver( + (config) => config.defaultDateOrder, + DEFAULT_DATE_ORDER +); + +/** + * Resolves an hour cycle using explicit time/date props, config defaults, or the library fallback. + */ +export function resolveHourCycleProp( + getTimeFormat: Getter, + getHourCycle: Getter +): Active { + const config = getTerraConfig(); + return readableActive(() => { + const timeFormat = getTimeFormat(); + if (timeFormat !== undefined) return timeFormatToHourCycle(timeFormat); + + const hourCycle = getHourCycle(); + if (hourCycle !== undefined) return hourCycle; + + const configTimeFormat = config.defaultTimeFormat.current; + if (configTimeFormat !== undefined) return timeFormatToHourCycle(configTimeFormat); + + return timeFormatToHourCycle(DEFAULT_TIME_FORMAT); + }); +} diff --git a/src/uix/terra/base/config/terra-config.ts b/src/uix/terra/base/config/terra-config.ts index edcd14a0a..298420115 100644 --- a/src/uix/terra/base/config/terra-config.ts +++ b/src/uix/terra/base/config/terra-config.ts @@ -1,49 +1,49 @@ -import { Context } from 'runed'; -import { readableActive, type Active, type ActiveProps } from '../../utils'; -import type { TerraConfigPropsWithoutChildren } from './types'; - -type TerraConfigStateProps = ActiveProps; - -export const TerraConfigContext = new Context('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; - - constructor(parent: TerraConfigState | null, opts: TerraConfigStateProps) { - const resolveConfigOption = createConfigResolver(parent, opts); - this.opts = { - defaultPortalTo: resolveConfigOption((config) => config.defaultPortalTo), - defaultLocale: resolveConfigOption((config) => config.defaultLocale), - defaultDateOrder: resolveConfigOption((config) => config.defaultDateOrder), - defaultTimeFormat: resolveConfigOption((config) => config.defaultTimeFormat), - }; - } -} - -type ConfigOptionGetter = (config: TerraConfigStateProps) => Active | undefined; -type ConfigOptionResolver = (getter: ConfigOptionGetter) => Active; - -function createConfigResolver( - parent: TerraConfigState | null, - currentOpts: TerraConfigStateProps -): ConfigOptionResolver { - return (getter: ConfigOptionGetter) => { - const configOption = readableActive(() => { - const value = getter(currentOpts)?.current; - if (value !== undefined) return value; - if (parent === null) return undefined; - return getter(parent.opts)?.current; - }); - return configOption; - }; -} - +import { Context } from 'runed'; +import { readableActive, type Active, type ActiveProps } from '$terra/utils'; +import type { TerraConfigPropsWithoutChildren } from './types'; + +type TerraConfigStateProps = ActiveProps; + +export const TerraConfigContext = new Context('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; + + 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), + }; + } +} + +type ConfigOptionGetter = (config: TerraConfigStateProps) => Active | undefined; +type ConfigOptionResolver = (getter: ConfigOptionGetter) => Active; + +function createConfigResolver( + parent: TerraConfigState | null, + currentOpts: TerraConfigStateProps +): ConfigOptionResolver { + return (getter: ConfigOptionGetter) => { + const configOption = readableActive(() => { + const value = getter(currentOpts)?.current; + if (value !== undefined) return value; + if (parent === null) return undefined; + return getter(parent.opts)?.current; + }); + return configOption; + }; +} diff --git a/src/uix/terra/base/config/types.ts b/src/uix/terra/base/config/types.ts index 56bcba1e9..8311dab5d 100644 --- a/src/uix/terra/base/config/types.ts +++ b/src/uix/terra/base/config/types.ts @@ -1,61 +1,76 @@ -import type { WithChildren } from '../../utils'; -import type { DateOrder, HourCycle } from '$terra/adapters/dates'; -import type { PortalTarget } from '../portal/types.ts'; - -export type TerraConfigPropsWithoutChildren = { - /** - * The default portal `to`/target to use for the `Portal` components throughout the app. - */ - defaultPortalTo?: PortalTarget; - - /** - * The default locale to use for the components that support localization. - */ - defaultLocale?: string; - - /** - * The default date part order to use for date-aware components. - */ - defaultDateOrder?: DateOrder; - - /** - * The default time format to use for time-aware components. - */ - defaultTimeFormat?: HourCycle; -}; - -export type TerraConfigProps = WithChildren; - -export type TerraDatePresentationProps = { - /** - * The locale to use for formatting date-aware components. - * - * @defaultValue 'en' - */ - locale?: string; - - /** - * Preferred order for date segments in date-aware components. - * - * @defaultValue 'DMY' - */ - dateOrder?: DateOrder; -}; - -export type TerraDateTimePresentationProps = TerraDatePresentationProps & { - /** - * The format to use for displaying time segments. - * - * @defaultValue the locale's default time format - */ - hourCycle?: HourCycle; - - /** - * Preferred time format for time-aware components. - * When provided, it overrides `hourCycle`. - * - * @defaultValue '24h' - */ - timeFormat?: HourCycle; -}; - +import type { Direction, WithChildren } from '$terra/utils'; +import type { DateOrder, HourCycle } from '$terra/adapters/dates'; +import type { PortalTarget } from '../portal/types.ts'; + +export type TerraConfigPropsWithoutChildren = { + /** + * The default portal `to`/target to use for the `Portal` components throughout the app. + */ + defaultPortalTo?: PortalTarget; + + /** + * The default locale to use for the components that support localization. + */ + defaultLocale?: string; + + /** + * The default direction to use for direction-aware components. + * + * @defaultValue 'ltr' + */ + defaultDir?: Direction; + + /** + * The default date part order to use for date-aware components. + */ + defaultDateOrder?: DateOrder; + + /** + * The default time format to use for time-aware components. + */ + defaultTimeFormat?: HourCycle; +}; + +export type TerraConfigProps = WithChildren; + +export type TerraPresentationProps = { + /** + * The locale to use for formatting localized components. + * + * @defaultValue 'en' + */ + locale?: string; + + /** + * The direction to use for direction-aware components. + * + * @defaultValue 'ltr' + */ + dir?: Direction; +}; + +export type TerraDatePresentationProps = TerraPresentationProps & { + /** + * Preferred order for date segments in date-aware components. + * + * @defaultValue 'DMY' + */ + dateOrder?: DateOrder; +}; + +export type TerraDateTimePresentationProps = TerraDatePresentationProps & { + /** + * The format to use for displaying time segments. + * + * @defaultValue the locale's default time format + */ + hourCycle?: HourCycle; + + /** + * Preferred time format for time-aware components. + * When provided, it overrides `hourCycle`. + * + * @defaultValue '24h' + */ + timeFormat?: HourCycle; +}; diff --git a/src/uix/terra/base/direction/direction.svelte.ts b/src/uix/terra/base/direction/direction.svelte.ts new file mode 100644 index 000000000..84a5ac3b1 --- /dev/null +++ b/src/uix/terra/base/direction/direction.svelte.ts @@ -0,0 +1,12 @@ +import { readableActive, type Active, type Direction } from '$terra/utils'; +import { getTerraConfig } from '../config/terra-config.ts'; + +export function getDirection(): Active { + const config = getTerraConfig(); + return readableActive(() => config.defaultDir.current ?? 'ltr'); +} + +export function useDirection(dir?: Active): Active { + const defaultDirection = getDirection(); + return readableActive(() => dir?.current ?? defaultDirection.current); +} diff --git a/src/uix/terra/base/direction/exports.ts b/src/uix/terra/base/direction/exports.ts new file mode 100644 index 000000000..01937dd65 --- /dev/null +++ b/src/uix/terra/base/direction/exports.ts @@ -0,0 +1 @@ +export { getDirection, useDirection } from './direction.svelte.ts'; diff --git a/src/uix/terra/base/direction/index.ts b/src/uix/terra/base/direction/index.ts new file mode 100644 index 000000000..4e6db6aa3 --- /dev/null +++ b/src/uix/terra/base/direction/index.ts @@ -0,0 +1 @@ +export * from './exports.ts'; diff --git a/src/uix/terra/base/index.ts b/src/uix/terra/base/index.ts index 78c5e5972..c51750751 100644 --- a/src/uix/terra/base/index.ts +++ b/src/uix/terra/base/index.ts @@ -1 +1 @@ -export { default as Mounted } from "./mounted.svelte"; +export { default as Mounted } from './mounted.svelte'; diff --git a/src/uix/terra/index.ts b/src/uix/terra/index.ts index 90db3ce00..adae80cc6 100644 --- a/src/uix/terra/index.ts +++ b/src/uix/terra/index.ts @@ -7,7 +7,7 @@ export * as AspectRatio from './aspect-ratio'; export * as Avatar from './avatar'; -export type { ImageLoadingStatus } from './avatar'; +export type { ImageLoadingStatus } from './utils'; export * as Accordion from './accordion'; export * as Button from './button'; export * as Checkbox from './checkbox'; @@ -49,6 +49,7 @@ export { TerraConfig } from './base/config/exports'; export type { TerraConfigProps, TerraConfigPropsWithoutChildren, + TerraPresentationProps, TerraDatePresentationProps, TerraDateTimePresentationProps, } from './base/config/exports'; @@ -63,9 +64,3 @@ export * as TimeField from './time-field'; export * as TimeRangeField from './time-range-field'; export * as DateRangeField from './date-range-field'; export * as DateRangePicker from './date-range-picker'; - - - - - -