From 10f7760dfdc176a9f2f821271c31ab60b57395b7 Mon Sep 17 00:00:00 2001 From: Ahmed Yasin Koculu Date: Mon, 6 Apr 2026 03:58:38 +0200 Subject: [PATCH] add generic type support for pval.shape --- package.json | 2 +- src/app/propValidators.ts | 32 ++++++++++++++--- tests/app/component-head.spec.ts | 59 ++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ff051d8..eed4eea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "regor", - "version": "1.5.1", + "version": "1.5.2", "description": "A modern UI framework for web and desktop applications, inspired by Vue's concepts and powered by simplicity and flexibility.", "author": "Ahmed Yasin Koculu", "license": "MIT", diff --git a/src/app/propValidators.ts b/src/app/propValidators.ts index 6837e6e..aad03e0 100644 --- a/src/app/propValidators.ts +++ b/src/app/propValidators.ts @@ -47,6 +47,9 @@ type ValidatorTuple = readonly [ ] type InferValidatorValue = TValidator extends PropValidator ? TValue : never +type ShapeSchemaFor = { + [TKey in keyof TShape]?: PropValidator +} /** * Validation schema shape suggested by `ComponentHead.props`. @@ -392,16 +395,37 @@ export const arrayOf = ( /** * Validates that a prop is an object and applies validators to selected nested * keys. Nested failures include dotted prop paths such as `meta.slug`. + * + * `shape(...)` supports both inferred literal schemas and explicit generic + * target types for editor completion: + * + * ```ts + * pval.shape({ + * tabId: pval.isNumber, + * }) + * + * pval.shape({ + * tabId: pval.isNumber, + * }) + * ``` */ -export const shape = ( +export function shape( + schema: ShapeSchemaFor, +): PropValidator +export function shape( schema: TSchema, -): PropValidator> => { +): PropValidator> +export function shape( + schema: Record | undefined>, +): PropValidator> { return (value, name, head) => { if (!isObject(value)) fail(name, `expected object, ${describe(value)}`) const record = value as Record for (const key in schema) { - const validator: PropValidator = schema[key] - validator(record[key], `${name}.${key}`, head) + const validator: PropValidator | undefined = schema[key] + if (!validator) continue + const validateField: PropValidator = validator + validateField(record[key], `${name}.${key}`, head) } } } diff --git a/tests/app/component-head.spec.ts b/tests/app/component-head.spec.ts index 45f1704..e546866 100644 --- a/tests/app/component-head.spec.ts +++ b/tests/app/component-head.spec.ts @@ -344,6 +344,65 @@ test('component head validator utilities support nested or validation inside sha ) }) +test('component head validator utilities support explicit generic shape schemas', () => { + type TabPaneType = { + tabId: number + title: string + closable?: boolean + } + + interface TabPaneInterface { + tabId: number + title: string + disabled?: boolean + } + + class TabPaneClass { + tabId = 0 + title = '' + visible?: boolean + } + + const host = document.createElement('div') + const start = document.createComment('s') + const end = document.createComment('e') + const head = new ComponentHead( + { + typePane: { + tabId: 1, + title: 'Type pane', + }, + interfacePane: { + tabId: 2, + title: 'Interface pane', + }, + classPane: { + tabId: 3, + title: 'Class pane', + }, + }, + host, + [], + start, + end, + 'throw', + ) + + expect(() => + head.validateProps({ + typePane: pval.shape({ + tabId: pval.isNumber, + }), + interfacePane: pval.shape({ + tabId: pval.isNumber, + }), + classPane: pval.shape({ + tabId: pval.isNumber, + }), + }), + ).not.toThrow() +}) + test('component head validateProps accepts custom user validators', () => { const host = document.createElement('div') const start = document.createComment('s')