@whatsaaaa/form-builder-affects 0.1.2 → 0.2.0
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/definition.schema.json +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/definition.schema.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://whatsaaaa.github.io/form-builder/schema/v1/affects/definition.schema.json",
|
|
3
4
|
"title": "Form definition",
|
|
4
5
|
"description": "A form for @whatsaaaa/form-builder-schema with the affects of @whatsaaaa/form-builder-affects. Run validate() too: it also checks names, defaults, form rule paths, layout references, and the fields that conditions read.",
|
|
5
6
|
"type": "object",
|
package/dist/index.d.ts
CHANGED
|
@@ -43,10 +43,12 @@ interface Affect {
|
|
|
43
43
|
declare const affectsProps: v.ObjectSchema<{
|
|
44
44
|
readonly affects: v.OptionalSchema<v.ArraySchema<v.LazySchema<v.GenericSchema<Affect>>, undefined>, undefined>;
|
|
45
45
|
}, undefined>;
|
|
46
|
+
/** The schema of the key the package adds, as `AffectsExtension` has it. */
|
|
47
|
+
type AffectsPropsSchema = typeof affectsProps;
|
|
46
48
|
/** The extension `affects()` gives, with the type of the key it adds, for `defineForm()`. */
|
|
47
49
|
export interface AffectsExtension extends SchemaExtension {
|
|
48
|
-
readonly fieldProps:
|
|
49
|
-
readonly layoutProps:
|
|
50
|
+
readonly fieldProps: AffectsPropsSchema;
|
|
51
|
+
readonly layoutProps: AffectsPropsSchema;
|
|
50
52
|
}
|
|
51
53
|
/**
|
|
52
54
|
* Conditions on fields, steps and sections: `affects`, a list of effects (`show`, `hide`,
|
|
@@ -55,5 +57,5 @@ export interface AffectsExtension extends SchemaExtension {
|
|
|
55
57
|
* hidden fields, and the builder's `getVisibility()` tells renderers what to hide and to clear.
|
|
56
58
|
*/
|
|
57
59
|
export declare function affects(): AffectsExtension;
|
|
58
|
-
export type { Affect, Condition, Effect, Scalar };
|
|
60
|
+
export type { Affect, AffectsPropsSchema, Condition, Effect, Scalar };
|
|
59
61
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/definition/affect.ts","../src/evaluate/evaluate.ts","../src/check/check-affects.ts","../src/index.ts"],"sourcesContent":["import * as v from 'valibot'\n\n/**\n * What an affect does while its condition is met: `hide` hides the field and keeps its value,\n * `hideAndClear` hides it and clears its value, and `show` shows it only then. Hide always wins.\n */\nexport type Effect = 'show' | 'hide' | 'hideAndClear'\n\n/** The value an operator compares with. */\nexport type Scalar = string | number | boolean\n\n/**\n * When an affect applies: a test of one field, by its dot path, with one operator, or every\n * (`all`) or any (`any`) of a list of conditions.\n */\nexport type Condition =\n | { field: string; equals: Scalar }\n | { field: string; notEquals: Scalar }\n | { field: string; in: readonly Scalar[] }\n | { field: string; includes: Scalar }\n | { field: string; empty: true }\n | { field: string; notEmpty: true }\n | { all: readonly Condition[] }\n | { any: readonly Condition[] }\n\n/** One effect of a field, step or section, and when it applies. */\nexport interface Affect {\n effect: Effect\n when: Condition\n}\n\n/** The operators of a test, each with the value it takes. */\nexport const OPERATORS = ['equals', 'notEquals', 'in', 'includes', 'empty', 'notEmpty'] as const\n\nexport type Operator = (typeof OPERATORS)[number]\n\nexport const EFFECTS = ['show', 'hide', 'hideAndClear'] as const satisfies readonly Effect[]\n\n// These schemas type `affects` in `defineForm()`, and make its JSON Schema. `validate()` checks a\n// definition with the package's own checks, which give each issue its code and path.\n\nconst path = v.pipe(v.string(), v.minLength(1))\nconst scalar = v.union([v.string(), v.number(), v.boolean()])\nconst conditions = v.pipe(v.array(v.lazy(() => condition)), v.minLength(1))\n\nexport const condition: v.GenericSchema<Condition> = v.union([\n v.strictObject({ field: path, equals: scalar }),\n v.strictObject({ field: path, notEquals: scalar }),\n v.strictObject({ field: path, in: v.pipe(v.array(scalar), v.minLength(1)) }),\n v.strictObject({ field: path, includes: scalar }),\n v.strictObject({ field: path, empty: v.literal(true) }),\n v.strictObject({ field: path, notEmpty: v.literal(true) }),\n v.strictObject({ all: conditions }),\n v.strictObject({ any: conditions }),\n])\n\nexport const affect: v.GenericSchema<Affect> = v.strictObject({\n effect: v.picklist(EFFECTS),\n when: v.lazy(() => condition),\n})\n\n/** The key the package adds to fields, steps and sections. */\nexport const affectsProps = v.object({\n affects: v.optional(v.array(v.lazy(() => affect))),\n})\n","import { coerceInput, type KindDef, type ValueKind } from '@whatsaaaa/form-builder-schema'\nimport type { Effect, Operator, Scalar } from '../definition/affect'\n\n/** A condition, checked, with each test reading its field by the index of the read. */\nexport type Compiled =\n | { readonly all: readonly Compiled[] }\n | { readonly any: readonly Compiled[] }\n | CompiledTest\n\nexport interface CompiledTest {\n /** The index of the field it reads, in the reads of the target. */\n readonly read: number\n readonly operator: Operator\n /** The value `equals`, `notEquals` and `includes` compare with, or the values of `in`. */\n readonly value: Scalar | readonly Scalar[] | true\n /** The kinds of the field it reads, to turn its input into its value, e.g. `'5'` into `5`. */\n readonly kind: ValueKind\n readonly itemKind: ValueKind | undefined\n readonly kindDef: KindDef\n}\n\n/**\n * Whether a condition is met. `read(i)` gives the input of the field of read `i`, or undefined\n * while it is hidden: a hidden field is empty.\n *\n * An empty field never equals a value, and so is not equal to any: `notEquals` is true for it.\n */\nexport function test(condition: Compiled, read: (i: number) => unknown): boolean {\n if ('all' in condition) return condition.all.every((each) => test(each, read))\n if ('any' in condition) return condition.any.some((each) => test(each, read))\n\n const { operator, value, kind, itemKind, kindDef } = condition\n // What the form holds, the way a renderer sets it: '5' is 5 for a number.\n const input = coerceInput(read(condition.read), { kind, itemKind })\n const empty = input === undefined || kindDef.isEmpty(input)\n switch (operator) {\n case 'empty':\n return empty\n case 'notEmpty':\n return !empty\n case 'equals':\n return !empty && input === value\n case 'notEquals':\n return empty || input !== value\n case 'in':\n return !empty && (value as readonly Scalar[]).includes(input as Scalar)\n case 'includes':\n return !empty && Array.isArray(input) && input.includes(value)\n }\n}\n\n/**\n * Whether the affects of a target hide it, and clear it: an unmet `show`, a met `hide` or a met\n * `hideAndClear` hides it, so hide always wins, and only a met `hideAndClear` clears it.\n */\nexport function stateOf(\n effects: Readonly<Partial<Record<Effect, Compiled>>>,\n read: (i: number) => unknown,\n): { hidden: boolean; clear: boolean } {\n const shown = effects.show === undefined || test(effects.show, read)\n const hidden = effects.hide !== undefined && test(effects.hide, read)\n const clear = effects.hideAndClear !== undefined && test(effects.hideAndClear, read)\n return { hidden: !shown || hidden || clear, clear }\n}\n","import type {\n DefinitionPath,\n ExtensionContext,\n ExtensionTarget,\n ResolvedField,\n TargetConditions,\n} from '@whatsaaaa/form-builder-schema'\nimport * as v from 'valibot'\nimport { EFFECTS, type Effect, OPERATORS, type Operator, type Scalar } from '../definition/affect'\nimport { type Compiled, type CompiledTest, stateOf } from '../evaluate/evaluate'\n\n/** The kinds whose values a test can compare: a value is one string, number or boolean. */\nconst SCALAR_KINDS: readonly string[] = ['string', 'number', 'boolean', 'date']\n\n/**\n * Checks the `affects` of every field, step and section, and adds their issues. Returns the\n * conditions of each one whose affects have no issues.\n */\nexport function checkAffects(\n ctx: ExtensionContext,\n): ReadonlyMap<ExtensionTarget, TargetConditions> {\n const found = new Map<ExtensionTarget, TargetConditions>()\n for (const target of ctx.targets) {\n if (!Object.hasOwn(target.json, 'affects')) continue\n const conditions = checkTarget(target, ctx)\n if (conditions) found.set(target, conditions)\n }\n return found\n}\n\n/** The conditions of one target, or undefined when its affects have issues. */\nfunction checkTarget(target: ExtensionTarget, ctx: ExtensionContext): TargetConditions | undefined {\n const issues = createIssues(ctx)\n const path = [...target.definitionPath, 'affects']\n const raw = target.json['affects']\n if (!Array.isArray(raw)) {\n issues.add('invalid_shape', path, '\"affects\" is a list of { \"effect\", \"when\" }')\n return undefined\n }\n if (isItemField(target)) {\n const message = `The field of a list's items has no \"affects\" of its own. Put them on the list`\n issues.add('invalid_shape', path, message)\n return undefined\n }\n\n const reads: TargetConditions['reads'][number][] = []\n const effects: Partial<Record<Effect, Compiled>> = {}\n raw.forEach((affect: unknown, j) => {\n const at = [...path, j]\n if (!isObject(affect)) {\n issues.add('invalid_shape', at, 'An affect is an object with \"effect\" and \"when\"')\n return\n }\n issues.unknownKeys(affect, ['effect', 'when'], at, 'an affect')\n const effect = affect['effect']\n if (!EFFECTS.includes(effect as Effect)) {\n issues.add('invalid_shape', [...at, 'effect'], '\"effect\" is \"show\", \"hide\" or \"hideAndClear\"')\n } else if (Object.hasOwn(effects, effect as Effect)) {\n const message = `\"${effect as string}\" is in \"affects\" already. Join its conditions with \"all\" or \"any\"`\n issues.add('invalid_shape', at, message)\n }\n if (!Object.hasOwn(affect, 'when')) {\n issues.add('invalid_shape', at, 'An affect needs \"when\", its condition')\n return\n }\n const compiled = checkCondition(affect['when'], [...at, 'when'], target, ctx, issues, reads)\n if (compiled && EFFECTS.includes(effect as Effect)) effects[effect as Effect] ??= compiled\n })\n\n if (issues.count) return undefined\n return { reads, evaluate: (read) => stateOf(effects, read) }\n}\n\n/**\n * Checks a condition, and compiles it, adding each field it reads to `reads`. Undefined when it\n * has issues.\n */\nfunction checkCondition(\n raw: unknown,\n at: DefinitionPath,\n target: ExtensionTarget,\n ctx: ExtensionContext,\n issues: Issues,\n reads: TargetConditions['reads'][number][],\n): Compiled | undefined {\n if (!isObject(raw)) {\n const message = 'A condition is an object: \"field\" with an operator, \"all\" or \"any\"'\n issues.add('invalid_shape', at, message)\n return undefined\n }\n const groups = (['all', 'any'] as const).filter((key) => Object.hasOwn(raw, key))\n const operators = OPERATORS.filter((key) => Object.hasOwn(raw, key))\n const kinds = [...groups, ...(Object.hasOwn(raw, 'field') ? ['field'] : [])]\n if (kinds.length > 1) {\n const keys = kinds.map((key) => `\"${key}\"`).join(' and ')\n issues.add('invalid_shape', at, `A condition has one of \"all\", \"any\" and \"field\", not ${keys}`)\n return undefined\n }\n\n const [group] = groups\n if (group) {\n issues.unknownKeys(raw, [group], at, 'a condition')\n const list = raw[group]\n if (!Array.isArray(list) || !list.length) {\n issues.add('invalid_shape', [...at, group], `\"${group}\" is a list of at least one condition`)\n return undefined\n }\n const compiled = list.map((each: unknown, k) =>\n checkCondition(each, [...at, group, k], target, ctx, issues, reads),\n )\n if (!compiled.every((each) => each !== undefined)) return undefined\n return group === 'all' ? { all: compiled } : { any: compiled }\n }\n\n const unknown = issues.unknownKeys(raw, ['field', ...OPERATORS], at, 'a condition')\n const field = raw['field']\n if (typeof field !== 'string' || !field) {\n issues.add(\n 'invalid_shape',\n [...at, 'field'],\n '\"field\" is the dot path of a field, like \"address.city\"',\n )\n }\n const [operator, other] = operators\n if (other) {\n const keys = operators.map((key) => `\"${key}\"`).join(', ')\n issues.add('invalid_shape', at, `A condition has one operator, not ${keys}`)\n } else if (!operator && !unknown) {\n const message = `A condition needs an operator: ${OPERATORS.map((key) => `\"${key}\"`).join(', ')}`\n issues.add('invalid_shape', at, message)\n }\n if (typeof field !== 'string' || !field || !operator || other) return undefined\n const value = checkValueShape(operator, raw[operator], [...at, operator], issues)\n if (value === undefined) return undefined\n\n const resolved = ctx.resolve(field, target)\n if (typeof resolved === 'string') issues.add('unknown_field', [...at, 'field'], resolved)\n if (typeof resolved === 'string' || resolved === null) return undefined\n if (!checkKind(operator, resolved, field, [...at, operator], issues)) return undefined\n if (!checkValues(operator, value, resolved, field, [...at, operator], issues)) return undefined\n\n reads.push({ field: resolved, at: [...at, 'field'] })\n return {\n read: reads.length - 1,\n operator,\n value,\n kind: resolved.kind,\n itemKind: resolved.item?.kind,\n kindDef: resolved.kindDef,\n } satisfies CompiledTest\n}\n\n/** The operator's value when it has the right shape, or undefined with an issue. */\nfunction checkValueShape(\n operator: Operator,\n value: unknown,\n at: DefinitionPath,\n issues: Issues,\n): CompiledTest['value'] | undefined {\n switch (operator) {\n case 'empty':\n case 'notEmpty':\n if (value === true) return true\n issues.add(\n 'invalid_shape',\n at,\n `\"${operator}\" is true. For the opposite, use \"${operator === 'empty' ? 'notEmpty' : 'empty'}\"`,\n )\n return undefined\n case 'in':\n if (Array.isArray(value) && value.length && value.every(isScalar)) return value\n issues.add('invalid_shape', at, '\"in\" is a list of at least one string, number or boolean')\n return undefined\n default:\n if (isScalar(value)) return value\n issues.add('invalid_shape', at, `\"${operator}\" is a string, a number or a boolean`)\n return undefined\n }\n}\n\n/** True when the operator fits the field's kind. Adds a `kind_mismatch` issue when it doesn't. */\nfunction checkKind(\n operator: Operator,\n field: ResolvedField,\n dotPath: string,\n at: DefinitionPath,\n issues: Issues,\n): boolean {\n const { kind, item } = field\n let fits: boolean\n let what = `${kind} fields`\n if (operator === 'empty' || operator === 'notEmpty') {\n // A group is never empty: it always holds its fields.\n fits = kind !== 'object'\n } else if (operator === 'includes') {\n fits = kind === 'array' && !!item && SCALAR_KINDS.includes(item.kind)\n what = kind !== 'array' ? what : `lists of ${item ? `${item.kind} items` : 'objects'}`\n } else {\n fits = SCALAR_KINDS.includes(kind)\n }\n if (!fits)\n issues.add('kind_mismatch', at, `\"${operator}\" can't be used on ${what}, like \"${dotPath}\"`)\n return fits\n}\n\n/**\n * True when the field can hold each value the operator compares with: its schema, or that of its\n * items for `includes`, takes it, and it isn't empty. Adds an `invalid_value` issue for each it\n * can't.\n */\nfunction checkValues(\n operator: Operator,\n value: CompiledTest['value'],\n field: ResolvedField,\n dotPath: string,\n at: DefinitionPath,\n issues: Issues,\n): boolean {\n if (operator === 'empty' || operator === 'notEmpty') return true\n const schema =\n operator === 'includes'\n ? (field.item as NonNullable<ResolvedField['item']>).schema\n : field.schema\n const values = operator === 'in' ? (value as readonly Scalar[]) : [value as Scalar]\n let valid = true\n values.forEach((each, k) => {\n const where = operator === 'in' ? [...at, k] : at\n if (operator !== 'includes' && field.kindDef.isEmpty(each)) {\n const message = `${JSON.stringify(each)} is no value, so it never matches. Use \"empty\" or \"notEmpty\"`\n issues.add('invalid_value', where, message)\n valid = false\n return\n }\n const result = v.safeParse(schema, each)\n if (result.success) return\n const reason = result.issues[0].message\n issues.add('invalid_value', where, `\"${dotPath}\" can't hold ${JSON.stringify(each)}: ${reason}`)\n valid = false\n })\n return valid\n}\n\n/** True for the field of a list's items, which has a value and no name: its value is the item. */\nfunction isItemField(target: ExtensionTarget): boolean {\n return (\n target.kind === 'field' &&\n target.field.field.name === undefined &&\n target.field.kind !== 'none' &&\n target.field.dataPath.at(-1) === '*'\n )\n}\n\ninterface Issues {\n /** How many issues this target has. */\n readonly count: number\n add(code: Parameters<ExtensionContext['issue']>[0], path: DefinitionPath, message: string): void\n /** Adds an issue for each key of `raw` that isn't `known`. True when there are some. */\n unknownKeys(\n raw: Record<string, unknown>,\n known: readonly string[],\n at: DefinitionPath,\n what: string,\n ): boolean\n}\n\nfunction createIssues(ctx: ExtensionContext): Issues {\n let count = 0\n const issues: Issues = {\n get count() {\n return count\n },\n add(code, path, message) {\n count++\n ctx.issue(code, path, message)\n },\n unknownKeys(raw, known, at, what) {\n const unknown = Object.keys(raw).filter((key) => !known.includes(key))\n for (const key of unknown)\n issues.add('unknown_key', [...at, key], `Unknown key \"${key}\" on ${what}`)\n return unknown.length > 0\n },\n }\n return issues\n}\n\nconst isObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst isScalar = (value: unknown): value is Scalar =>\n typeof value === 'string' ||\n (typeof value === 'number' && Number.isFinite(value)) ||\n typeof value === 'boolean'\n","// Public API. Everything a consumer can import is exported here and nowhere else.\nimport type { SchemaExtension } from '@whatsaaaa/form-builder-schema'\nimport { checkAffects } from './check/check-affects'\nimport { affect, affectsProps, condition } from './definition/affect'\n\nexport type { Affect, Condition, Effect, Scalar } from './definition/affect'\n\n/** The extension `affects()` gives, with the type of the key it adds, for `defineForm()`. */\nexport interface AffectsExtension extends SchemaExtension {\n readonly fieldProps: typeof affectsProps\n readonly layoutProps: typeof affectsProps\n}\n\n/**\n * Conditions on fields, steps and sections: `affects`, a list of effects (`show`, `hide`,\n * `hideAndClear`), each with the condition that applies it. Pass it to the schema builder:\n * `createSchemaBuilder({ extensions: [affects()] })`. The schema it builds skips the validation of\n * hidden fields, and the builder's `getVisibility()` tells renderers what to hide and to clear.\n */\nexport function affects(): AffectsExtension {\n return {\n name: 'affects',\n fieldProps: affectsProps,\n layoutProps: affectsProps,\n definitions: { affect, condition },\n check: checkAffects,\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/definition/affect.ts","../src/evaluate/evaluate.ts","../src/check/check-affects.ts","../src/index.ts"],"sourcesContent":["import * as v from 'valibot'\n\n/**\n * What an affect does while its condition is met: `hide` hides the field and keeps its value,\n * `hideAndClear` hides it and clears its value, and `show` shows it only then. Hide always wins.\n */\nexport type Effect = 'show' | 'hide' | 'hideAndClear'\n\n/** The value an operator compares with. */\nexport type Scalar = string | number | boolean\n\n/**\n * When an affect applies: a test of one field, by its dot path, with one operator, or every\n * (`all`) or any (`any`) of a list of conditions.\n */\nexport type Condition =\n | { field: string; equals: Scalar }\n | { field: string; notEquals: Scalar }\n | { field: string; in: readonly Scalar[] }\n | { field: string; includes: Scalar }\n | { field: string; empty: true }\n | { field: string; notEmpty: true }\n | { all: readonly Condition[] }\n | { any: readonly Condition[] }\n\n/** One effect of a field, step or section, and when it applies. */\nexport interface Affect {\n effect: Effect\n when: Condition\n}\n\n/** The operators of a test, each with the value it takes. */\nexport const OPERATORS = ['equals', 'notEquals', 'in', 'includes', 'empty', 'notEmpty'] as const\n\nexport type Operator = (typeof OPERATORS)[number]\n\nexport const EFFECTS = ['show', 'hide', 'hideAndClear'] as const satisfies readonly Effect[]\n\n// These schemas type `affects` in `defineForm()`, and make its JSON Schema. `validate()` checks a\n// definition with the package's own checks, which give each issue its code and path.\n\nconst path = v.pipe(v.string(), v.minLength(1))\nconst scalar = v.union([v.string(), v.number(), v.boolean()])\nconst conditions = v.pipe(v.array(v.lazy(() => condition)), v.minLength(1))\n\nexport const condition: v.GenericSchema<Condition> = v.union([\n v.strictObject({ field: path, equals: scalar }),\n v.strictObject({ field: path, notEquals: scalar }),\n v.strictObject({ field: path, in: v.pipe(v.array(scalar), v.minLength(1)) }),\n v.strictObject({ field: path, includes: scalar }),\n v.strictObject({ field: path, empty: v.literal(true) }),\n v.strictObject({ field: path, notEmpty: v.literal(true) }),\n v.strictObject({ all: conditions }),\n v.strictObject({ any: conditions }),\n])\n\nexport const affect: v.GenericSchema<Affect> = v.strictObject({\n effect: v.picklist(EFFECTS),\n when: v.lazy(() => condition),\n})\n\n/** The key the package adds to fields, steps and sections. */\nexport const affectsProps = v.object({\n affects: v.optional(v.array(v.lazy(() => affect))),\n})\n\n/** The schema of the key the package adds, as `AffectsExtension` has it. */\nexport type AffectsPropsSchema = typeof affectsProps\n","import { coerceInput, type KindDef, type ValueKind } from '@whatsaaaa/form-builder-schema'\nimport type { Effect, Operator, Scalar } from '../definition/affect'\n\n/** A condition, checked, with each test reading its field by the index of the read. */\nexport type Compiled =\n | { readonly all: readonly Compiled[] }\n | { readonly any: readonly Compiled[] }\n | CompiledTest\n\nexport interface CompiledTest {\n /** The index of the field it reads, in the reads of the target. */\n readonly read: number\n readonly operator: Operator\n /** The value `equals`, `notEquals` and `includes` compare with, or the values of `in`. */\n readonly value: Scalar | readonly Scalar[] | true\n /** The kinds of the field it reads, to turn its input into its value, e.g. `'5'` into `5`. */\n readonly kind: ValueKind\n readonly itemKind: ValueKind | undefined\n readonly kindDef: KindDef\n}\n\n/**\n * Whether a condition is met. `read(i)` gives the input of the field of read `i`, or undefined\n * while it is hidden: a hidden field is empty.\n *\n * An empty field never equals a value, and so is not equal to any: `notEquals` is true for it.\n */\nexport function test(condition: Compiled, read: (i: number) => unknown): boolean {\n if ('all' in condition) return condition.all.every((each) => test(each, read))\n if ('any' in condition) return condition.any.some((each) => test(each, read))\n\n const { operator, value, kind, itemKind, kindDef } = condition\n // What the form holds, the way a renderer sets it: '5' is 5 for a number.\n const input = coerceInput(read(condition.read), { kind, itemKind })\n const empty = input === undefined || kindDef.isEmpty(input)\n switch (operator) {\n case 'empty':\n return empty\n case 'notEmpty':\n return !empty\n case 'equals':\n return !empty && input === value\n case 'notEquals':\n return empty || input !== value\n case 'in':\n return !empty && (value as readonly Scalar[]).includes(input as Scalar)\n case 'includes':\n return !empty && Array.isArray(input) && input.includes(value)\n }\n}\n\n/**\n * Whether the affects of a target hide it, and clear it: an unmet `show`, a met `hide` or a met\n * `hideAndClear` hides it, so hide always wins, and only a met `hideAndClear` clears it.\n */\nexport function stateOf(\n effects: Readonly<Partial<Record<Effect, Compiled>>>,\n read: (i: number) => unknown,\n): { hidden: boolean; clear: boolean } {\n const shown = effects.show === undefined || test(effects.show, read)\n const hidden = effects.hide !== undefined && test(effects.hide, read)\n const clear = effects.hideAndClear !== undefined && test(effects.hideAndClear, read)\n return { hidden: !shown || hidden || clear, clear }\n}\n","import type {\n DefinitionPath,\n ExtensionContext,\n ExtensionTarget,\n ResolvedField,\n TargetConditions,\n} from '@whatsaaaa/form-builder-schema'\nimport * as v from 'valibot'\nimport { EFFECTS, type Effect, OPERATORS, type Operator, type Scalar } from '../definition/affect'\nimport { type Compiled, type CompiledTest, stateOf } from '../evaluate/evaluate'\n\n/** The kinds whose values a test can compare: a value is one string, number or boolean. */\nconst SCALAR_KINDS: readonly string[] = ['string', 'number', 'boolean', 'date']\n\n/**\n * Checks the `affects` of every field, step and section, and adds their issues. Returns the\n * conditions of each one whose affects have no issues.\n */\nexport function checkAffects(\n ctx: ExtensionContext,\n): ReadonlyMap<ExtensionTarget, TargetConditions> {\n const found = new Map<ExtensionTarget, TargetConditions>()\n for (const target of ctx.targets) {\n if (!Object.hasOwn(target.json, 'affects')) continue\n const conditions = checkTarget(target, ctx)\n if (conditions) found.set(target, conditions)\n }\n return found\n}\n\n/** The conditions of one target, or undefined when its affects have issues. */\nfunction checkTarget(target: ExtensionTarget, ctx: ExtensionContext): TargetConditions | undefined {\n const issues = createIssues(ctx)\n const path = [...target.definitionPath, 'affects']\n const raw = target.json['affects']\n if (!Array.isArray(raw)) {\n issues.add('invalid_shape', path, '\"affects\" is a list of { \"effect\", \"when\" }')\n return undefined\n }\n if (isItemField(target)) {\n const message = `The field of a list's items has no \"affects\" of its own. Put them on the list`\n issues.add('invalid_shape', path, message)\n return undefined\n }\n\n const reads: TargetConditions['reads'][number][] = []\n const effects: Partial<Record<Effect, Compiled>> = {}\n raw.forEach((affect: unknown, j) => {\n const at = [...path, j]\n if (!isObject(affect)) {\n issues.add('invalid_shape', at, 'An affect is an object with \"effect\" and \"when\"')\n return\n }\n issues.unknownKeys(affect, ['effect', 'when'], at, 'an affect')\n const effect = affect['effect']\n if (!EFFECTS.includes(effect as Effect)) {\n issues.add('invalid_shape', [...at, 'effect'], '\"effect\" is \"show\", \"hide\" or \"hideAndClear\"')\n } else if (Object.hasOwn(effects, effect as Effect)) {\n const message = `\"${effect as string}\" is in \"affects\" already. Join its conditions with \"all\" or \"any\"`\n issues.add('invalid_shape', at, message)\n }\n if (!Object.hasOwn(affect, 'when')) {\n issues.add('invalid_shape', at, 'An affect needs \"when\", its condition')\n return\n }\n const compiled = checkCondition(affect['when'], [...at, 'when'], target, ctx, issues, reads)\n if (compiled && EFFECTS.includes(effect as Effect)) effects[effect as Effect] ??= compiled\n })\n\n if (issues.count) return undefined\n return { reads, evaluate: (read) => stateOf(effects, read) }\n}\n\n/**\n * Checks a condition, and compiles it, adding each field it reads to `reads`. Undefined when it\n * has issues.\n */\nfunction checkCondition(\n raw: unknown,\n at: DefinitionPath,\n target: ExtensionTarget,\n ctx: ExtensionContext,\n issues: Issues,\n reads: TargetConditions['reads'][number][],\n): Compiled | undefined {\n if (!isObject(raw)) {\n const message = 'A condition is an object: \"field\" with an operator, \"all\" or \"any\"'\n issues.add('invalid_shape', at, message)\n return undefined\n }\n const groups = (['all', 'any'] as const).filter((key) => Object.hasOwn(raw, key))\n const operators = OPERATORS.filter((key) => Object.hasOwn(raw, key))\n const kinds = [...groups, ...(Object.hasOwn(raw, 'field') ? ['field'] : [])]\n if (kinds.length > 1) {\n const keys = kinds.map((key) => `\"${key}\"`).join(' and ')\n issues.add('invalid_shape', at, `A condition has one of \"all\", \"any\" and \"field\", not ${keys}`)\n return undefined\n }\n\n const [group] = groups\n if (group) {\n issues.unknownKeys(raw, [group], at, 'a condition')\n const list = raw[group]\n if (!Array.isArray(list) || !list.length) {\n issues.add('invalid_shape', [...at, group], `\"${group}\" is a list of at least one condition`)\n return undefined\n }\n const compiled = list.map((each: unknown, k) =>\n checkCondition(each, [...at, group, k], target, ctx, issues, reads),\n )\n if (!compiled.every((each) => each !== undefined)) return undefined\n return group === 'all' ? { all: compiled } : { any: compiled }\n }\n\n const unknown = issues.unknownKeys(raw, ['field', ...OPERATORS], at, 'a condition')\n const field = raw['field']\n if (typeof field !== 'string' || !field) {\n issues.add(\n 'invalid_shape',\n [...at, 'field'],\n '\"field\" is the dot path of a field, like \"address.city\"',\n )\n }\n const [operator, other] = operators\n if (other) {\n const keys = operators.map((key) => `\"${key}\"`).join(', ')\n issues.add('invalid_shape', at, `A condition has one operator, not ${keys}`)\n } else if (!operator && !unknown) {\n const message = `A condition needs an operator: ${OPERATORS.map((key) => `\"${key}\"`).join(', ')}`\n issues.add('invalid_shape', at, message)\n }\n if (typeof field !== 'string' || !field || !operator || other) return undefined\n const value = checkValueShape(operator, raw[operator], [...at, operator], issues)\n if (value === undefined) return undefined\n\n const resolved = ctx.resolve(field, target)\n if (typeof resolved === 'string') issues.add('unknown_field', [...at, 'field'], resolved)\n if (typeof resolved === 'string' || resolved === null) return undefined\n if (!checkKind(operator, resolved, field, [...at, operator], issues)) return undefined\n if (!checkValues(operator, value, resolved, field, [...at, operator], issues)) return undefined\n\n reads.push({ field: resolved, at: [...at, 'field'] })\n return {\n read: reads.length - 1,\n operator,\n value,\n kind: resolved.kind,\n itemKind: resolved.item?.kind,\n kindDef: resolved.kindDef,\n } satisfies CompiledTest\n}\n\n/** The operator's value when it has the right shape, or undefined with an issue. */\nfunction checkValueShape(\n operator: Operator,\n value: unknown,\n at: DefinitionPath,\n issues: Issues,\n): CompiledTest['value'] | undefined {\n switch (operator) {\n case 'empty':\n case 'notEmpty':\n if (value === true) return true\n issues.add(\n 'invalid_shape',\n at,\n `\"${operator}\" is true. For the opposite, use \"${operator === 'empty' ? 'notEmpty' : 'empty'}\"`,\n )\n return undefined\n case 'in':\n if (Array.isArray(value) && value.length && value.every(isScalar)) return value\n issues.add('invalid_shape', at, '\"in\" is a list of at least one string, number or boolean')\n return undefined\n default:\n if (isScalar(value)) return value\n issues.add('invalid_shape', at, `\"${operator}\" is a string, a number or a boolean`)\n return undefined\n }\n}\n\n/** True when the operator fits the field's kind. Adds a `kind_mismatch` issue when it doesn't. */\nfunction checkKind(\n operator: Operator,\n field: ResolvedField,\n dotPath: string,\n at: DefinitionPath,\n issues: Issues,\n): boolean {\n const { kind, item } = field\n let fits: boolean\n let what = `${kind} fields`\n if (operator === 'empty' || operator === 'notEmpty') {\n // A group is never empty: it always holds its fields.\n fits = kind !== 'object'\n } else if (operator === 'includes') {\n fits = kind === 'array' && !!item && SCALAR_KINDS.includes(item.kind)\n what = kind !== 'array' ? what : `lists of ${item ? `${item.kind} items` : 'objects'}`\n } else {\n fits = SCALAR_KINDS.includes(kind)\n }\n if (!fits)\n issues.add('kind_mismatch', at, `\"${operator}\" can't be used on ${what}, like \"${dotPath}\"`)\n return fits\n}\n\n/**\n * True when the field can hold each value the operator compares with: its schema, or that of its\n * items for `includes`, takes it, and it isn't empty. Adds an `invalid_value` issue for each it\n * can't.\n */\nfunction checkValues(\n operator: Operator,\n value: CompiledTest['value'],\n field: ResolvedField,\n dotPath: string,\n at: DefinitionPath,\n issues: Issues,\n): boolean {\n if (operator === 'empty' || operator === 'notEmpty') return true\n const schema =\n operator === 'includes'\n ? (field.item as NonNullable<ResolvedField['item']>).schema\n : field.schema\n const values = operator === 'in' ? (value as readonly Scalar[]) : [value as Scalar]\n let valid = true\n values.forEach((each, k) => {\n const where = operator === 'in' ? [...at, k] : at\n if (operator !== 'includes' && field.kindDef.isEmpty(each)) {\n const message = `${JSON.stringify(each)} is no value, so it never matches. Use \"empty\" or \"notEmpty\"`\n issues.add('invalid_value', where, message)\n valid = false\n return\n }\n const result = v.safeParse(schema, each)\n if (result.success) return\n const reason = result.issues[0].message\n issues.add('invalid_value', where, `\"${dotPath}\" can't hold ${JSON.stringify(each)}: ${reason}`)\n valid = false\n })\n return valid\n}\n\n/** True for the field of a list's items, which has a value and no name: its value is the item. */\nfunction isItemField(target: ExtensionTarget): boolean {\n return (\n target.kind === 'field' &&\n target.field.field.name === undefined &&\n target.field.kind !== 'none' &&\n target.field.dataPath.at(-1) === '*'\n )\n}\n\ninterface Issues {\n /** How many issues this target has. */\n readonly count: number\n add(code: Parameters<ExtensionContext['issue']>[0], path: DefinitionPath, message: string): void\n /** Adds an issue for each key of `raw` that isn't `known`. True when there are some. */\n unknownKeys(\n raw: Record<string, unknown>,\n known: readonly string[],\n at: DefinitionPath,\n what: string,\n ): boolean\n}\n\nfunction createIssues(ctx: ExtensionContext): Issues {\n let count = 0\n const issues: Issues = {\n get count() {\n return count\n },\n add(code, path, message) {\n count++\n ctx.issue(code, path, message)\n },\n unknownKeys(raw, known, at, what) {\n const unknown = Object.keys(raw).filter((key) => !known.includes(key))\n for (const key of unknown)\n issues.add('unknown_key', [...at, key], `Unknown key \"${key}\" on ${what}`)\n return unknown.length > 0\n },\n }\n return issues\n}\n\nconst isObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst isScalar = (value: unknown): value is Scalar =>\n typeof value === 'string' ||\n (typeof value === 'number' && Number.isFinite(value)) ||\n typeof value === 'boolean'\n","// Public API. Everything a consumer can import is exported here and nowhere else.\nimport type { SchemaExtension } from '@whatsaaaa/form-builder-schema'\nimport { checkAffects } from './check/check-affects'\nimport { affect, affectsProps, type AffectsPropsSchema, condition } from './definition/affect'\n\nexport type { Affect, AffectsPropsSchema, Condition, Effect, Scalar } from './definition/affect'\n\n/** The extension `affects()` gives, with the type of the key it adds, for `defineForm()`. */\nexport interface AffectsExtension extends SchemaExtension {\n readonly fieldProps: AffectsPropsSchema\n readonly layoutProps: AffectsPropsSchema\n}\n\n/**\n * Conditions on fields, steps and sections: `affects`, a list of effects (`show`, `hide`,\n * `hideAndClear`), each with the condition that applies it. Pass it to the schema builder:\n * `createSchemaBuilder({ extensions: [affects()] })`. The schema it builds skips the validation of\n * hidden fields, and the builder's `getVisibility()` tells renderers what to hide and to clear.\n */\nexport function affects(): AffectsExtension {\n return {\n name: 'affects',\n fieldProps: affectsProps,\n layoutProps: affectsProps,\n definitions: { affect, condition },\n check: checkAffects,\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@whatsaaaa/form-builder-affects",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Show, hide and clear the fields, sections and steps of JSON form definitions by conditions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"conditional",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@size-limit/preset-small-lib": "^14.3.0",
|
|
40
40
|
"@valibot/to-json-schema": "^1.8.0",
|
|
41
41
|
"@vitest/coverage-v8": "^5.0.5",
|
|
42
|
-
"@whatsaaaa/form-builder-schema": "^0.
|
|
42
|
+
"@whatsaaaa/form-builder-schema": "^0.17.0",
|
|
43
43
|
"ajv": "^8.18.0",
|
|
44
44
|
"jiti": "^2.7.0",
|
|
45
45
|
"publint": "^0.3.25",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"vitest": "^5.0.5"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"@whatsaaaa/form-builder-schema": "^0.
|
|
53
|
+
"@whatsaaaa/form-builder-schema": "^0.17.0",
|
|
54
54
|
"valibot": "^1.3.0"
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|