diff --git a/angular/headless/src/utils/widget.spec.ts b/angular/headless/src/utils/widget.spec.ts index 4b3164ed77..5f27563359 100644 --- a/angular/headless/src/utils/widget.spec.ts +++ b/angular/headless/src/utils/widget.spec.ts @@ -47,6 +47,7 @@ describe('callWidgetFactoryWithConfig', () => { { onMyAction: typeFunction, myValue: typeString, + onCounterChange: undefined, }, ); const derivedValue$ = computed(createZoneCheckFn('computeDerivedValue', () => `derived from ${myValue$()}`)); diff --git a/core-bootstrap/src/components/alert/alert.ts b/core-bootstrap/src/components/alert/alert.ts index a2b8ce1d46..b5800841fc 100644 --- a/core-bootstrap/src/components/alert/alert.ts +++ b/core-bootstrap/src/components/alert/alert.ts @@ -1,10 +1,10 @@ import type {AlertDirectives, AlertState as CoreState, AlertProps as CoreProps, AlertApi} from '@agnos-ui/core/components/alert'; import {createAlert as createCoreAlert, getAlertDefaultConfig as getCoreDefaultConfig} from '@agnos-ui/core/components/alert'; import type {ConfigValidator, SlotContent, Widget, WidgetFactory, WidgetSlotContext} from '@agnos-ui/core/types'; -import {typeString} from '@agnos-ui/core/utils/writables'; +import {createTypeEnum} from '@agnos-ui/core/utils/writables'; import {extendWidgetProps} from '@agnos-ui/core/services/extendWidget'; import {fadeTransition} from '../../services/transitions'; -import type {BSContextualClass} from '../../types'; +import {BS_CONTEXTUAL_CLASSES, type BSContextualClass} from '../../types'; import type {TransitionFn} from '@agnos-ui/core/services/transitions/baseTransitions'; export * from '@agnos-ui/core/components/alert'; @@ -51,7 +51,9 @@ const coreOverride: Partial = { transition: fadeTransition, }; const configValidator: ConfigValidator = { - type: typeString as any, + type: createTypeEnum(BS_CONTEXTUAL_CLASSES), + structure: undefined, + children: undefined, }; /** diff --git a/core-bootstrap/src/components/collapse/collapse.ts b/core-bootstrap/src/components/collapse/collapse.ts index eaf68adfc9..5837010a54 100644 --- a/core-bootstrap/src/components/collapse/collapse.ts +++ b/core-bootstrap/src/components/collapse/collapse.ts @@ -2,7 +2,7 @@ import {createTransition} from '@agnos-ui/core/services/transitions/baseTransiti import type {ConfigValidator, Directive, PropsConfig, Widget} from '@agnos-ui/core/types'; import {stateStores, writablesForProps} from '@agnos-ui/core/utils/stores'; import {bindDirectiveNoArg} from '@agnos-ui/core/utils/directive'; -import {typeBoolean} from '@agnos-ui/core/utils/writables'; +import {typeBoolean, typeFunction, typeString} from '@agnos-ui/core/utils/writables'; import {collapseHorizontalTransition, collapseVerticalTransition} from '../../services/transitions/collapse'; import {asWritable, computed} from '@amadeus-it-group/tansu'; @@ -126,6 +126,13 @@ export function getCollapseDefaultConfig(): CollapseProps { const commonCollapseConfigValidator: ConfigValidator = { horizontal: typeBoolean, + onVisibleChange: typeFunction, + onHidden: typeFunction, + onShown: typeFunction, + animatedOnInit: typeBoolean, + animated: typeBoolean, + className: typeString, + visible: typeBoolean, }; /** diff --git a/core-bootstrap/src/components/modal/modal.ts b/core-bootstrap/src/components/modal/modal.ts index 4ed99ec295..bfedb99362 100644 --- a/core-bootstrap/src/components/modal/modal.ts +++ b/core-bootstrap/src/components/modal/modal.ts @@ -90,6 +90,12 @@ const coreOverride: Partial = { const configValidator: ConfigValidator> = { fullscreen: typeBoolean, + contentData: undefined, + children: undefined, + footer: undefined, + header: undefined, + structure: undefined, + title: undefined, }; /** * Retrieve a shallow copy of the default modal config diff --git a/core-bootstrap/src/components/pagination/pagination.ts b/core-bootstrap/src/components/pagination/pagination.ts index 9753e0169f..4b3e5ae64d 100644 --- a/core-bootstrap/src/components/pagination/pagination.ts +++ b/core-bootstrap/src/components/pagination/pagination.ts @@ -129,4 +129,4 @@ export function getPaginationDefaultConfig(): PaginationProps { * @param config - an optional alert config * @returns a PaginationWidget */ -export const createPagination: WidgetFactory = extendWidgetProps(createCorePagination, defaultConfigExtraProps, {}); +export const createPagination: WidgetFactory = extendWidgetProps(createCorePagination, defaultConfigExtraProps); diff --git a/core-bootstrap/src/components/progressbar/progressbar.ts b/core-bootstrap/src/components/progressbar/progressbar.ts index 65afc0d5af..e5518e8d8f 100644 --- a/core-bootstrap/src/components/progressbar/progressbar.ts +++ b/core-bootstrap/src/components/progressbar/progressbar.ts @@ -1,9 +1,9 @@ import type {ProgressbarDirectives, ProgressbarState as CoreState, ProgressbarProps as CoreProps} from '@agnos-ui/core/components/progressbar'; import {createProgressbar as createCoreProgressbar, getProgressbarDefaultConfig as getCoreDefaultConfig} from '@agnos-ui/core/components/progressbar'; import type {ConfigValidator, SlotContent, Widget, WidgetFactory, WidgetSlotContext} from '@agnos-ui/core/types'; -import {typeBoolean, typeString} from '@agnos-ui/core/utils/writables'; +import {createTypeEnum, typeBoolean, typeString} from '@agnos-ui/core/utils/writables'; import {extendWidgetProps} from '@agnos-ui/core/services/extendWidget'; -import type {BSContextualClass} from '../../types'; +import {BS_CONTEXTUAL_CLASSES, type BSContextualClass} from '../../types'; export * from '@agnos-ui/core/components/progressbar'; @@ -61,7 +61,9 @@ const configValidator: ConfigValidator = { height: typeString, striped: typeBoolean, animated: typeBoolean, - type: typeString as any, + type: createTypeEnum([undefined, ...BS_CONTEXTUAL_CLASSES]), + structure: undefined, + children: undefined, }; /** diff --git a/core-bootstrap/src/components/rating/rating.ts b/core-bootstrap/src/components/rating/rating.ts index 79d80476eb..902002a05d 100644 --- a/core-bootstrap/src/components/rating/rating.ts +++ b/core-bootstrap/src/components/rating/rating.ts @@ -40,4 +40,4 @@ export function getRatingDefaultConfig(): RatingProps { * @returns a RatingWidget */ -export const createRating: WidgetFactory = extendWidgetProps(createCoreRating, defaultConfigExtraProps, {}); +export const createRating: WidgetFactory = extendWidgetProps(createCoreRating, defaultConfigExtraProps); diff --git a/core-bootstrap/src/components/select/select.ts b/core-bootstrap/src/components/select/select.ts index af7ad7f064..f37d41f02d 100644 --- a/core-bootstrap/src/components/select/select.ts +++ b/core-bootstrap/src/components/select/select.ts @@ -67,5 +67,4 @@ export function getSelectDefaultConfig(): SelectProps { export const createSelect: (config?: PropsConfig>) => SelectWidget = extendWidgetProps( createCoreSelect, defaultConfigExtraProps, - {}, ) as any; diff --git a/core-bootstrap/src/components/slider/slider.ts b/core-bootstrap/src/components/slider/slider.ts index d2a3691ac2..25ed8c535a 100644 --- a/core-bootstrap/src/components/slider/slider.ts +++ b/core-bootstrap/src/components/slider/slider.ts @@ -66,4 +66,4 @@ export function getSliderDefaultConfig(): SliderProps { * @returns a SliderWidget */ -export const createSlider: WidgetFactory = extendWidgetProps(createCoreSlider, defaultConfigExtraProps, {}); +export const createSlider: WidgetFactory = extendWidgetProps(createCoreSlider, defaultConfigExtraProps); diff --git a/core-bootstrap/src/components/toast/toast.ts b/core-bootstrap/src/components/toast/toast.ts index a88aee349f..0d8b55ecbe 100644 --- a/core-bootstrap/src/components/toast/toast.ts +++ b/core-bootstrap/src/components/toast/toast.ts @@ -60,4 +60,13 @@ export function getToastDefaultConfig(): ToastProps { * @param config - an optional alert config * @returns an ToastWidget */ -export const createToast: WidgetFactory = extendWidgetProps(createCoreToast, defaultConfigExtraProps, {}, coreOverride); +export const createToast: WidgetFactory = extendWidgetProps( + createCoreToast, + defaultConfigExtraProps, + { + structure: undefined, + children: undefined, + header: undefined, + }, + coreOverride, +); diff --git a/core-bootstrap/src/types.ts b/core-bootstrap/src/types.ts index e49683e742..266ae3f450 100644 --- a/core-bootstrap/src/types.ts +++ b/core-bootstrap/src/types.ts @@ -1,2 +1,13 @@ // Enumeration of all eight bootstrap contextual classes export type BSContextualClass = 'success' | 'info' | 'warning' | 'danger' | 'primary' | 'secondary' | 'light' | 'dark'; + +export const BS_CONTEXTUAL_CLASSES: BSContextualClass[] = Object.values({ + success: 'success', + info: 'info', + warning: 'warning', + danger: 'danger', + primary: 'primary', + secondary: 'secondary', + light: 'light', + dark: 'dark', +} satisfies {[K in BSContextualClass]: K}); diff --git a/core/src/components/alert/common.ts b/core/src/components/alert/common.ts index 1dfe7e8cd4..e0aa2de1ea 100644 --- a/core/src/components/alert/common.ts +++ b/core/src/components/alert/common.ts @@ -5,7 +5,7 @@ import type {ConfigValidator, Directive, PropsConfig, Widget} from '../../types' import {noop} from '../../utils/internal/func'; import {stateStores, writablesForProps} from '../../utils/stores'; import {bindDirectiveNoArg} from '../../utils/directive'; -import {typeBoolean} from '../../utils/writables'; +import {typeBoolean, typeFunction, typeString} from '../../utils/writables'; export interface CommonAlertCommonPropsAndState extends WidgetsCommonPropsAndState { /** @@ -144,6 +144,15 @@ export function getCommonAlertDefaultConfig(): CommonAlertProps { const commonAlertConfigValidator: ConfigValidator = { dismissible: typeBoolean, + onVisibleChange: typeFunction, + onHidden: typeFunction, + onShown: typeFunction, + transition: typeFunction, + animatedOnInit: typeBoolean, + animated: typeBoolean, + visible: typeBoolean, + ariaCloseButtonLabel: typeString, + className: typeString, }; /** diff --git a/core/src/components/pagination/pagination.ts b/core/src/components/pagination/pagination.ts index f17ccf1537..17c27a52dd 100644 --- a/core/src/components/pagination/pagination.ts +++ b/core/src/components/pagination/pagination.ts @@ -1,9 +1,8 @@ import {computed} from '@amadeus-it-group/tansu'; import type {ReadableSignal} from '@amadeus-it-group/tansu'; -import {INVALID_VALUE} from '../../types'; import {bindableProp, stateStores, writablesForProps} from '../../utils/stores'; import {clamp, isNumber} from '../../utils/internal/checks'; -import {typeBoolean, typeFunction, typeNumber, typeString} from '../../utils/writables'; +import {createTypeEnum, typeBoolean, typeFunction, typeNumber, typeString} from '../../utils/writables'; import type {ConfigValidator, PropsConfig, Widget, Directive} from '../../types'; import {noop} from '../../utils/internal/func'; import type {WidgetsCommonPropsAndState} from '../commonProps'; @@ -373,7 +372,7 @@ const configValidator: ConfigValidator = { disabled: typeBoolean, directionLinks: typeBoolean, boundaryLinks: typeBoolean, - size: {normalizeValue: (value) => (value === 'lg' || value === 'sm' || value === null ? value : INVALID_VALUE)}, + size: createTypeEnum(['lg', 'sm', null]), onPageChange: typeFunction, pagesFactory: typeFunction, ariaLabel: typeString, @@ -386,6 +385,7 @@ const configValidator: ConfigValidator = { ariaLiveLabel: typeFunction, className: typeString, pageLink: typeFunction, + ariaEllipsisLabel: typeString, }; /** diff --git a/core/src/components/slider/slider.ts b/core/src/components/slider/slider.ts index 8f9b305500..4994eca11e 100644 --- a/core/src/components/slider/slider.ts +++ b/core/src/components/slider/slider.ts @@ -6,7 +6,7 @@ import type {ConfigValidator, Directive, PropsConfig, Widget} from '../../types' import {noop} from '../../utils/internal/func'; import {getDecimalPrecision} from '../../utils/internal/math'; import {bindableProp, stateStores, writablesForProps} from '../../utils/stores'; -import {typeArray, typeBoolean, typeFunction, typeNumber, typeNumberInRangeFactory} from '../../utils/writables'; +import {typeArray, typeBoolean, typeFunction, typeNumber, typeNumberInRangeFactory, typeString} from '../../utils/writables'; import {createResizeObserver} from '../../services/resizeObserver'; export interface ProgressDisplayOptions { @@ -317,6 +317,7 @@ const configValidator: ConfigValidator = { showValueLabels: typeBoolean, showMinMaxLabels: typeBoolean, rtl: typeBoolean, + className: typeString, }; /** diff --git a/core/src/types.ts b/core/src/types.ts index 5a8b3f63c9..7f74d93200 100644 --- a/core/src/types.ts +++ b/core/src/types.ts @@ -101,7 +101,7 @@ export interface WritableWithDefaultOptions { equal?: StoreOptions['equal']; } -export type ConfigValidator = {[K in keyof T]?: WritableWithDefaultOptions}; +export type ConfigValidator = {[K in keyof T]: WritableWithDefaultOptions | undefined}; export type AttributeValue = string | number | boolean | undefined; export type StyleKey = Exclude< diff --git a/core/src/utils/internal/checks.spec.ts b/core/src/utils/internal/checks.spec.ts index 032d708181..4b01cc26a1 100644 --- a/core/src/utils/internal/checks.spec.ts +++ b/core/src/utils/internal/checks.spec.ts @@ -1,5 +1,5 @@ import {describe, expect, test, vi} from 'vitest'; -import {allowNull, clamp, isArray, isBoolean, isFunction, isHTMLElement, isNumber, isString} from './checks'; +import {allowNull, clamp, isArray, isBoolean, isFromEnum, isFunction, isHTMLElement, isNumber, isString} from './checks'; describe('Checks', () => { test(`'isNumber' should check if value is a number`, () => { @@ -140,4 +140,11 @@ describe('Checks', () => { expect(withAllowNull(0)).toBe(false); expect(alwaysFalse).toHaveBeenCalledTimes(2); }); + + test(`'isFromEnum' should check if the value is part of the array`, () => { + const emptyEnumCheck = isFromEnum([]); + expect(emptyEnumCheck('value')).toBe(false); + const uniqEnumCheck = isFromEnum(['value']); + expect(uniqEnumCheck('value')).toBe(true); + }); }); diff --git a/core/src/utils/internal/checks.ts b/core/src/utils/internal/checks.ts index b55e521446..cd8dce562c 100644 --- a/core/src/utils/internal/checks.ts +++ b/core/src/utils/internal/checks.ts @@ -68,3 +68,12 @@ export const allowNull = (isType: (value: any) => value is T) => (value: any): value is T | null => value === null || isType(value); + +/** + * Builds a new type guard to check if an element belongs to an enum list + * @param list - the list of all enum values + * @returns the type guard + */ +export function isFromEnum(list: T[]) { + return (value: any): value is T => list.includes(value); +} diff --git a/core/src/utils/stores.spec.ts b/core/src/utils/stores.spec.ts index 90e2e958e3..60ee05fbae 100644 --- a/core/src/utils/stores.spec.ts +++ b/core/src/utils/stores.spec.ts @@ -336,6 +336,7 @@ describe(`Stores service`, () => { return value; }, }, + b: undefined, }, ); const a: number[] = []; diff --git a/core/src/utils/stores.ts b/core/src/utils/stores.ts index 1428eb2a56..50214dd740 100644 --- a/core/src/utils/stores.ts +++ b/core/src/utils/stores.ts @@ -258,7 +258,7 @@ export const writablesWithDefault = ( export const writablesForProps = ( defConfig: T, propsConfig?: PropsConfig, - options?: {[K in keyof T]?: WritableWithDefaultOptions}, + options?: {[K in keyof T]: WritableWithDefaultOptions | undefined}, ): [ToWritableSignal, ReturnType>] => { const stores = writablesWithDefault(defConfig, propsConfig, options); return [stores, createPatch(stores)]; diff --git a/core/src/utils/writables.ts b/core/src/utils/writables.ts index f3fdbfd51c..9d4e29ab6d 100644 --- a/core/src/utils/writables.ts +++ b/core/src/utils/writables.ts @@ -1,4 +1,4 @@ -import {allowNull, clamp, isArray, isBoolean, isFunction, isHTMLElement, isNumber, isString} from './internal/checks'; +import {allowNull, clamp, isArray, isBoolean, isFromEnum, isFunction, isHTMLElement, isNumber, isString} from './internal/checks'; import type {WritableWithDefaultOptions} from '../types'; import {INVALID_VALUE} from '../types'; @@ -91,3 +91,15 @@ export const typeArray: WritableWithDefaultOptions = { } }, }; + +/** + * Build an enum normalizer + * @template T - the enum type + * @param enumList - list of enum values to check + * @returns the enum normalizer + */ +export function createTypeEnum(enumList: T[]): WritableWithDefaultOptions { + return { + normalizeValue: testToNormalizeValue(isFromEnum(enumList)), + }; +}