regor 1.4.5 → 1.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -97,6 +97,156 @@ HTML:
97
97
  </div>
98
98
  ```
99
99
 
100
+ ## Component Props Validation
101
+
102
+ Regor components can validate incoming props at runtime inside `context(head)`.
103
+
104
+ This is opt-in and local to the component author:
105
+
106
+ - it does not change `defineComponent(...)`
107
+ - it validates only the keys you list
108
+ - it follows `config.propValidationMode`
109
+ - it does not coerce values
110
+ - it does not mutate `head.props`
111
+
112
+ Use `head.validateProps(...)` together with `pval`:
113
+
114
+ ```ts
115
+ import { defineComponent, html, pval } from 'regor'
116
+
117
+ type EditorCard = {
118
+ title: string
119
+ count?: number
120
+ mode: 'create' | 'edit'
121
+ summary?: string
122
+ }
123
+
124
+ const editorCard = defineComponent<EditorCard>(
125
+ html`<article>{{ summary }}</article>`,
126
+ {
127
+ props: ['title', 'count', 'mode'],
128
+ context: (head) => {
129
+ head.validateProps({
130
+ title: pval.isString,
131
+ count: pval.optional(pval.isNumber),
132
+ mode: pval.oneOf(['create', 'edit'] as const),
133
+ })
134
+
135
+ return {
136
+ ...head.props,
137
+ summary: `${head.props.title}:${head.props.mode}:${head.props.count ?? 'none'}`,
138
+ }
139
+ },
140
+ },
141
+ )
142
+ ```
143
+
144
+ ### Built-in validators
145
+
146
+ ```ts
147
+ import { pval } from 'regor'
148
+
149
+ pval.isString
150
+ pval.isNumber
151
+ pval.isBoolean
152
+ pval.isClass(MyClass)
153
+ pval.optional(pval.isString)
154
+ pval.nullable(pval.isNumber)
155
+ pval.oneOf(['create', 'edit'] as const)
156
+ pval.arrayOf(pval.isString)
157
+ pval.shape({ title: pval.isString, count: pval.isNumber })
158
+ pval.refOf(pval.isString)
159
+ pval.fail('title', 'expected non-empty string')
160
+ ```
161
+
162
+ ### Dynamic bindings and refs
163
+
164
+ Single-prop dynamic bindings like `:title="titleRef"` flow into component props as refs.
165
+ When validating those runtime values, use `pval.refOf(...)`:
166
+
167
+ ```ts
168
+ type CardProps = {
169
+ title: Ref<string>
170
+ summary?: string
171
+ }
172
+
173
+ const card = defineComponent<CardProps>(html`<h3>{{ summary }}</h3>`, {
174
+ props: ['title'],
175
+ context: (head) => {
176
+ head.validateProps({
177
+ title: pval.refOf(pval.isString),
178
+ })
179
+
180
+ return {
181
+ ...head.props,
182
+ summary: head.props.title(),
183
+ }
184
+ },
185
+ })
186
+ ```
187
+
188
+ For object-style `:context="{ ... }"` values, validate the plain runtime shape directly:
189
+
190
+ ```ts
191
+ head.validateProps({
192
+ meta: pval.shape({
193
+ slug: pval.isString,
194
+ }),
195
+ })
196
+ ```
197
+
198
+ ### Custom validators
199
+
200
+ Users can provide their own validators as long as they match the `PropValidator<T>` signature:
201
+
202
+ ```ts
203
+ import { pval, type PropValidator } from 'regor'
204
+
205
+ const isNonEmptyString: PropValidator<string> = (value, name) => {
206
+ if (typeof value !== 'string' || value.trim() === '') {
207
+ pval.fail(name, 'expected non-empty string')
208
+ }
209
+ }
210
+
211
+ head.validateProps({
212
+ title: isNonEmptyString,
213
+ })
214
+ ```
215
+
216
+ Custom validators can also use the third `head` argument:
217
+
218
+ ```ts
219
+ const startsWithPrefix: PropValidator<string> = (value, name, head) => {
220
+ const ctx = head.requireContext(AppServices)
221
+ if (typeof value !== 'string' || !value.startsWith(ctx.prefix)) {
222
+ pval.fail(name, 'expected prefixed value')
223
+ }
224
+ }
225
+ ```
226
+
227
+ ### Validation mode
228
+
229
+ Validation behavior is controlled through `RegorConfig.propValidationMode`:
230
+
231
+ ```ts
232
+ import { RegorConfig } from 'regor'
233
+
234
+ const config = new RegorConfig()
235
+ config.propValidationMode = 'warn'
236
+ ```
237
+
238
+ Available modes:
239
+
240
+ - `'throw'` (default): throw immediately on invalid prop
241
+ - `'warn'`: report through `warningHandler.warning(...)` and continue
242
+ - `'off'`: skip runtime prop validation entirely
243
+
244
+ Pass the config into `createApp(...)` when you want app-level control:
245
+
246
+ ```ts
247
+ createApp(appContext, template, config)
248
+ ```
249
+
100
250
  ## Table Templates and Components
101
251
 
102
252
  Regor preprocesses table-related templates to keep markup valid when using
@@ -223,6 +373,7 @@ These directives empower you to create dynamic and interactive user interfaces,
223
373
 
224
374
  - **`createApp`** Similar to Vue's `createApp`, it initializes a Regor application instance.
225
375
  - **`defineComponent`** Creates a Regor component instance.
376
+ - **`pval`** Built-in component prop validators used with `head.validateProps(...)`.
226
377
  - **`toFragment`** Converts a JSON template to a document fragment.
227
378
  - **`toJsonTemplate`** Converts a DOM element to a JSON template.
228
379
 
package/dist/regor.d.ts CHANGED
@@ -1,5 +1,86 @@
1
1
  // Generated by dts-bundle-generator v9.5.1
2
2
 
3
+ /**
4
+ * Assertion-style runtime validator used by `head.validateProps(...)`.
5
+ *
6
+ * A validator should throw when the value is invalid and return normally when
7
+ * the value satisfies the expected runtime contract.
8
+ *
9
+ * @typeParam TValue - Value type asserted by the validator when it succeeds.
10
+ * @param value - Raw incoming prop value.
11
+ * @param name - Prop name or nested path currently being validated.
12
+ * @param head - Current component head, useful for context-aware validation.
13
+ */
14
+ export type PropValidator<TValue = unknown> = (value: unknown, name: string, head: ComponentHead<any>) => asserts value is TValue;
15
+ export type ValidationSchemaLike = Record<string, PropValidator<any>>;
16
+ /**
17
+ * Validation schema shape suggested by `ComponentHead<T>.props`.
18
+ *
19
+ * Every key is optional so component authors can validate only the subset they
20
+ * care about. Editor completion is still driven by the known prop keys.
21
+ */
22
+ export type PropValidationSchemaFor<TProps extends object> = {
23
+ [TKey in keyof TProps]?: PropValidator<TProps[TKey]>;
24
+ };
25
+ /**
26
+ * Infers the asserted value types from a validation schema.
27
+ *
28
+ * Keys whose values are not validators are ignored.
29
+ */
30
+ export type InferPropValidationSchema<TSchema extends Record<string, unknown>> = {
31
+ [TKey in keyof TSchema as TSchema[TKey] extends PropValidator<any> ? TKey : never]: TSchema[TKey] extends PropValidator<infer TValue> ? TValue : never;
32
+ };
33
+ /**
34
+ * Built-in prop-validator namespace used with `head.validateProps(...)`.
35
+ *
36
+ * This namespace includes both ready-made validators and composition helpers.
37
+ * Custom validators can also use `pval.fail(...)` to produce the same
38
+ * structured failure shape as Regor's built-in validators.
39
+ *
40
+ * Example:
41
+ * ```ts
42
+ * head.validateProps({
43
+ * title: pval.isString,
44
+ * count: pval.optional(pval.isNumber),
45
+ * meta: pval.shape({
46
+ * slug: pval.isString,
47
+ * }),
48
+ * })
49
+ * ```
50
+ */
51
+ export declare const pval: {
52
+ readonly fail: (name: string, message: string) => never;
53
+ readonly isString: PropValidator<string>;
54
+ readonly isNumber: PropValidator<number>;
55
+ readonly isBoolean: PropValidator<boolean>;
56
+ readonly isClass: <TValue extends object>(ctor: abstract new (...args: any[]) => TValue) => PropValidator<TValue>;
57
+ readonly optional: <TValue>(validator: PropValidator<TValue>) => PropValidator<TValue | undefined>;
58
+ readonly nullable: <TValue>(validator: PropValidator<TValue>) => PropValidator<TValue | null>;
59
+ readonly oneOf: <const TValue extends readonly unknown[]>(values: TValue) => PropValidator<TValue[number]>;
60
+ readonly arrayOf: <TValue>(validator: PropValidator<TValue>) => PropValidator<TValue[]>;
61
+ readonly shape: <TSchema extends ValidationSchemaLike>(schema: TSchema) => PropValidator<InferPropValidationSchema<TSchema>>;
62
+ readonly refOf: <TValue>(validator: PropValidator<TValue>) => PropValidator<AnyRef>;
63
+ };
64
+ export type PropValidationMode = "throw" | "warn" | "off";
65
+ export declare class RegorConfig {
66
+ static getDefault(): RegorConfig;
67
+ forGrowThreshold: number;
68
+ globalContext: Record<string, unknown>;
69
+ useInterpolation: boolean;
70
+ /**
71
+ * Controls how `head.validateProps(...)` behaves when a validator fails.
72
+ *
73
+ * - `'throw'` (default): rethrows the validation error immediately.
74
+ * - `'warn'`: forwards the validation error to `warningHandler.warning(...)`
75
+ * and continues.
76
+ * - `'off'`: skips runtime prop validation entirely.
77
+ */
78
+ propValidationMode: PropValidationMode;
79
+ constructor(globalContext?: Record<string, unknown>);
80
+ addComponent<TContext extends IRegorContext | object = IRegorContext>(...components: Array<Component<TContext>>): void;
81
+ setDirectives(prefix: string): void;
82
+ updateDirectives(updater: (directiveMap: Record<string, Directive>, builtInNames: Record<string, string>) => void): void;
83
+ }
3
84
  export type ContextClass<TValue extends object> = abstract new (...args: never[]) => TValue;
4
85
  /**
5
86
  * Runtime metadata passed to a component's `context(head)` factory.
@@ -111,7 +192,12 @@ export declare class ComponentHead<TContext extends IRegorContext | object = IRe
111
192
  * Useful when post-assignment normalization is needed.
112
193
  */
113
194
  onAutoPropsAssigned?: () => void;
114
- constructor(props: TContext, element: Element, ctx: IRegorContext[], start: Comment, end: Comment);
195
+ /**
196
+ * Runtime behavior used when `validateProps(...)` encounters invalid input.
197
+ * Defaults to `'throw'`.
198
+ */
199
+ __propValidationMode: PropValidationMode;
200
+ constructor(props: TContext, element: Element, ctx: IRegorContext[], start: Comment, end: Comment, propValidationMode: PropValidationMode);
115
201
  /**
116
202
  * Emits a custom DOM event from the component host element.
117
203
  *
@@ -170,22 +256,45 @@ export declare class ComponentHead<TContext extends IRegorContext | object = IRe
170
256
  * @throws Error when no matching instance exists at the requested occurrence.
171
257
  */
172
258
  requireContext<TValue extends object>(constructor: ContextClass<TValue>, occurrence?: number): TValue;
259
+ /**
260
+ * Validates selected incoming props using assertion-style validators.
261
+ *
262
+ * Only keys listed in `schema` are checked. Validation throws immediately
263
+ * on the first invalid prop and does not mutate `head.props`.
264
+ *
265
+ * The schema is keyed from `head.props`, so editor completion can suggest
266
+ * known prop names while still allowing you to validate only a subset.
267
+ *
268
+ * Validators typically come from `pval`, but custom user validators are also
269
+ * supported. Custom validators may throw their own `Error`, though `pval.fail(...)`
270
+ * is recommended so nested validators can preserve the exact failing prop path.
271
+ *
272
+ * Example:
273
+ * ```ts
274
+ * head.validateProps({
275
+ * title: pval.isString,
276
+ * count: pval.optional(pval.isNumber),
277
+ * })
278
+ * ```
279
+ *
280
+ * Example with a custom validator:
281
+ * ```ts
282
+ * const isNonEmptyString: PropValidator<string> = (value, name) => {
283
+ * if (typeof value !== 'string' || value.trim() === '') {
284
+ * pval.fail(name, 'expected non-empty string')
285
+ * }
286
+ * }
287
+ * ```
288
+ *
289
+ * @param schema - Validators to apply to selected incoming props.
290
+ */
291
+ validateProps<TSchema extends PropValidationSchemaFor<TContext>>(schema: TSchema): asserts this is ComponentHead<TContext & InferPropValidationSchema<TSchema>>;
173
292
  /**
174
293
  * Unmounts this component instance by removing nodes between `start` and `end`
175
294
  * and calling unmount lifecycle handlers for captured contexts.
176
295
  */
177
296
  unmount(): void;
178
297
  }
179
- export declare class RegorConfig {
180
- static getDefault(): RegorConfig;
181
- forGrowThreshold: number;
182
- globalContext: Record<string, unknown>;
183
- useInterpolation: boolean;
184
- constructor(globalContext?: Record<string, unknown>);
185
- addComponent<TContext extends IRegorContext | object = IRegorContext>(...components: Array<Component<TContext>>): void;
186
- setDirectives(prefix: string): void;
187
- updateDirectives(updater: (directiveMap: Record<string, Directive>, builtInNames: Record<string, string>) => void): void;
188
- }
189
298
  export type IsNull<T> = [
190
299
  T
191
300
  ] extends [