From d4272366c4379dc9104c8cefc73f04c154a19b31 Mon Sep 17 00:00:00 2001 From: kevinkosterr Date: Thu, 11 Dec 2025 18:59:21 +0100 Subject: [PATCH 01/15] WIP: builder based forms start --- src/builders/base.ts | 192 ++++++++++++++++++ src/builders/fields/TextFieldBuilder.ts | 40 ++++ src/builders/group.ts | 21 ++ src/builders/index.ts | 102 ++++++++++ src/builders/schema.ts | 32 +++ src/resources/types/field/base.ts | 6 +- src/resources/types/generic.ts | 10 +- tests/builders/BaseFieldBuilder.spec.ts | 248 ++++++++++++++++++++++++ 8 files changed, 644 insertions(+), 7 deletions(-) create mode 100644 src/builders/base.ts create mode 100644 src/builders/fields/TextFieldBuilder.ts create mode 100644 src/builders/group.ts create mode 100644 src/builders/index.ts create mode 100644 src/builders/schema.ts create mode 100644 tests/builders/BaseFieldBuilder.spec.ts diff --git a/src/builders/base.ts b/src/builders/base.ts new file mode 100644 index 0000000..b0d40fe --- /dev/null +++ b/src/builders/base.ts @@ -0,0 +1,192 @@ +import type { Field } from '@/resources/types/field/fields' +import type { FieldBase } from '@/resources/types/field/base' + +export interface IBaseBuilder { + build (): T + extend (properties: Record): this + __data__ (): Partial +} + +/** + * The base class for every builder. + */ +export abstract class BaseBuilder implements IBaseBuilder { + + protected data: Partial + + protected constructor (type: string, name: string, model: string) { + this.data = { + type, name, model + } as Partial + } + + __data__ (): Partial { return this.data } + + /** + * Gets the keys required for this field. + * Should be overridden by child classes if they have additional required keys. + * + * @protected + */ + protected getRequiredKeys (): (keyof T)[] { + return [ 'type', 'name', 'model' ] as (keyof T)[] + } + + /** + * Extend the current field with additional properties. + * @param properties + */ + extend (properties: Record): this { + Object.assign(this.data, properties) + return this + } + + /** + * Build and validate the field object. + * Ensures all required properties are present. + */ + build (): T { + const requiredKeys: (keyof T)[] = this.getRequiredKeys() + const missingKeys: (keyof T)[] = requiredKeys.filter((key: keyof T) => !(key in this.data)) + + if (missingKeys.length) { + throw new Error(`Failed to build field "${this.data.name}". Missing required keys: ${missingKeys.join(', ')}`) + } + + return this.data as T + } + +} + +export interface IBaseFieldBuilder extends IBaseBuilder { + id (value: NonNullable): this + label(value: NonNullable): this + labelIcon (value: NonNullable): this + noLabel (value?: FieldBase['noLabel']): this + hint (value: NonNullable): this + required (value?: NonNullable): this + visible (value?: FieldBase['visible']): this + readonly (value?: FieldBase['readonly']): this + disabled (value?: FieldBase['disabled']): this +} + +/** + * Base class for most FieldBuilder classes. + */ +export abstract class BaseFieldBuilder extends BaseBuilder implements IBaseFieldBuilder { + + /** + * Set the id property of the field. + * @param value + */ + id (value: NonNullable): this { + this.data.id = value + return this + } + + /** + * Set the label property of the field. + * @param value + */ + label(value: NonNullable): this { + this.data.label = value + return this + } + + /** + * Set the labelIcon property of the field. + * @param value + */ + labelIcon (value: NonNullable): this { + this.data.labelIcon = value + return this + } + + /** + * Set the noLabel property of the field. + * @param value + */ + noLabel (value?: FieldBase['noLabel']): this { + if (value === undefined) value = true + this.data.noLabel = value + return this + } + + /** + * Set the hint property of the field. + * @param value + */ + hint (value: NonNullable): this { + this.data.hint = value + return this + } + + /** + * Set the required property of the field. + * @param value + */ + required (value?: NonNullable): this { + if (value === undefined) value = true + this.data.required = value + return this + } + + /** + * Set the visible property of the field. + * @param value + */ + visible (value?: FieldBase['visible']): this { + if (value === undefined) value = true + this.data.visible = value + return this + } + + /** + * Set the readonly property of the field. + * @param value + */ + readonly (value?: FieldBase['readonly']): this { + if (value === undefined) value = true + this.data.readonly = value + return this + } + + /** + * Set the disabled property of the field. + * @param value + */ + disabled (value?: FieldBase['disabled']): this { + if (value === undefined) value = true + this.data.disabled = value + return this + } + + /** + * Set the validator property of the field. + * @param value + */ + validator(value: NonNullable): this { + this.data.validator = value + return this + } + + /** + * Set the `validate` property of the field. This determines when the validation will take place. + * Defaults to `onBlur`. + * @param value + */ + validateOn (value: NonNullable): this { + this.data.validate = value + return this + } + + /** + * Set the onValidated property of the field. + * @param value + */ + onValidated (value: NonNullable) : this { + this.data.onValidated = value + return this + } + +} diff --git a/src/builders/fields/TextFieldBuilder.ts b/src/builders/fields/TextFieldBuilder.ts new file mode 100644 index 0000000..d3ce4f7 --- /dev/null +++ b/src/builders/fields/TextFieldBuilder.ts @@ -0,0 +1,40 @@ +import { BaseFieldBuilder, IBaseFieldBuilder } from '@/builders/base' +import type { TextField } from '@/resources/types/field/fields' + +export interface ITextFieldBuilder extends IBaseFieldBuilder { + placeholder (value: string): this + autoComplete (value?: boolean): this +} + +export default class TextFieldBuilder extends BaseFieldBuilder implements ITextFieldBuilder { + + constructor(name: string, model: string) { + super('input', name, model) + this.data.inputType = 'text' + } + + /** + * Sets the `placeholder` property. + * @param value + */ + placeholder (value: string): this { + this.data.placeholder = value + return this + } + + /** + * Sets the `autocomplete` property. + * @param value + */ + autoComplete (value?: boolean): this { + if (value === undefined) value = true + this.data.autocomplete = value + return this + } + + protected getRequiredKeys(): (keyof TextField)[] { + return [ ...super.getRequiredKeys(), 'inputType' ] + } + + +} diff --git a/src/builders/group.ts b/src/builders/group.ts new file mode 100644 index 0000000..93bc208 --- /dev/null +++ b/src/builders/group.ts @@ -0,0 +1,21 @@ +import { BaseFieldBuilder } from '@/builders/base' +import { FormGeneratorGroup } from '@/resources/types/generic' + + +export class GroupBuilder { + protected legend?: string = undefined + protected fields: BaseFieldBuilder[] = [] + + constructor(fields: BaseFieldBuilder[], legend?: string) { + this.fields = fields + this.legend = legend + } + + build (): FormGeneratorGroup { + return { + legend: this.legend, + fields: this.fields.map(field => field.build()) + } + } + +} diff --git a/src/builders/index.ts b/src/builders/index.ts new file mode 100644 index 0000000..f60171f --- /dev/null +++ b/src/builders/index.ts @@ -0,0 +1,102 @@ +import { GroupBuilder } from '@/builders/group' +import { SchemaBuilder } from '@/builders/schema' +import { BaseFieldBuilder } from '@/builders/base' + +import TextFieldBuilder from '@/builders/fields/TextFieldBuilder' + +import type { FormModel } from '@/resources/types/fieldAttributes' + +type SchemaItem = BaseFieldBuilder | GroupBuilder + +function schema(model: FormModel): SchemaBuilder +function schema(model: FormModel, ...items: BaseFieldBuilder[]): SchemaBuilder +function schema(model: FormModel, ...items: GroupBuilder[]): SchemaBuilder +function schema(model: FormModel, ...items: SchemaItem[]): SchemaBuilder +function schema( + model: FormModel, + ...items: SchemaItem[] +): SchemaBuilder { + if (!items || items.length === 0) { + return new SchemaBuilder(model, undefined, undefined) + } + + const fields: BaseFieldBuilder[] = [] + const groups: GroupBuilder[] = [] + + items.forEach(item => { + if (item instanceof GroupBuilder) { + groups.push(item) + } else { + fields.push(item as BaseFieldBuilder) + } + }) + + return new SchemaBuilder( + model, + fields.length > 0 ? fields : undefined, + groups.length > 0 ? groups : undefined + ) +} + +export const f = { + group: (legend?: string, ...fields: BaseFieldBuilder[])=> { + return new GroupBuilder(fields, legend) + }, + /** + * Returns a form schema builder instance. + * Takes a model object as the first argument. Takes field builders and group builders as the rest of the arguments. + * + * The order of field builders and group builders will be reflected inside the rendered form. + * + * @example + * // With field builders + * f.schema( + * { + * username: '' + * role: '' + * }, + * f.text('Username', 'username').placeholder('Enter your username'), + * f.select('Role', 'role').addOption('Admin', 'admin') + * ).build() + * + * // With groups + * f.schema( + * { + * username: '' + * role: '' + * }, + * f.group( + * 'User details', + * f.text('Username', 'username').placeholder('Enter your username'), + * f.select('Role', 'role').addOption('Admin', 'admin') + * ) + * ).build() + * + * // With groups and field builders + * f.schema( + * { + * username: '' + * role: '', + * terms: false + * }, + * f.group( + * 'User details', + * f.text('Username', 'username').placeholder('Enter your username'), + * f.select('Role', 'role').addOption('Admin', 'admin') + * ), + * f.checkbox('Terms and conditions', 'terms').label('I agree to the terms and conditions') + * ).build() + */ + schema, + /** + * @example + * // As builder + * f.text('fieldName', 'fieldModelKey').placeholder('Field placeholder') + * // As JSON + * f.text('fieldName', 'fieldModelKey').placeholder('Field placeholder').build() + * @param name + * @param model + */ + text: (name: string, model: string): TextFieldBuilder => new TextFieldBuilder(name, model) +} + diff --git a/src/builders/schema.ts b/src/builders/schema.ts new file mode 100644 index 0000000..365d9e3 --- /dev/null +++ b/src/builders/schema.ts @@ -0,0 +1,32 @@ +import { BaseFieldBuilder } from '@/builders/base' +import { FormGeneratorSchema } from '@/resources/types/generic' +import { GroupBuilder } from '@/builders/group' + +/** + * Form schema builder + * @param model - Initial model object. + * @param fields - Array of field builders. + * @param groups - Array of group builders. + */ +export class SchemaBuilder { + protected model: Record = {} + protected fields?: BaseFieldBuilder[] = [] + protected groups?: GroupBuilder[] = [] + + constructor(model: Record, fields?: BaseFieldBuilder[], groups?: GroupBuilder[]) { + this.model = model + this.fields = fields + this.groups = groups + } + + build (): FormGeneratorSchema { + return { + model: this.model, + schema: { + fields: this.fields?.map(field => field.build()) ?? undefined, + groups: this.groups?.map(group => group.build()) ?? undefined + } + } + } + +} \ No newline at end of file diff --git a/src/resources/types/field/base.ts b/src/resources/types/field/base.ts index 9a125d0..de0b2cf 100644 --- a/src/resources/types/field/base.ts +++ b/src/resources/types/field/base.ts @@ -4,12 +4,12 @@ import type { TOnValidatedFunction, TValidatorFunction } from '@/resources/types/functions' -import type { ComponentPublicInstance } from 'vue' +import type { Component } from 'vue' import type { FieldOption } from '@/resources/types/fieldAttributes' import type { ValidationTrigger } from '@/resources/types/generic' export type LabelIconDefinition = { - icon: string | ComponentPublicInstance; + icon: string | Component; position: 'left' | 'right'; } @@ -21,7 +21,7 @@ export type FieldBase = { name: string; model: string; label?: string; - labelIcon?: string | ComponentPublicInstance | LabelIconDefinition; + labelIcon?: string | Component | LabelIconDefinition; type: string; visible?: boolean | TDynamicAttributeBooleanFunction; required?: boolean | TDynamicAttributeBooleanFunction; diff --git a/src/resources/types/generic.ts b/src/resources/types/generic.ts index 2ffb06a..56f7901 100644 --- a/src/resources/types/generic.ts +++ b/src/resources/types/generic.ts @@ -24,14 +24,16 @@ export type FieldPluginOptions = { excludedComponents?: PluginOptions['excludedComponents']; } +export type FormGeneratorGroup = { + legend?: string; + fields: Field[]; +} + export type FormGeneratorSchema = { model: FormModel; schema: { fields?: Field[]; - groups?: { - legend: string; - fields: Field[]; - }[] + groups?: FormGeneratorGroup[]; }, } diff --git a/tests/builders/BaseFieldBuilder.spec.ts b/tests/builders/BaseFieldBuilder.spec.ts new file mode 100644 index 0000000..edd3560 --- /dev/null +++ b/tests/builders/BaseFieldBuilder.spec.ts @@ -0,0 +1,248 @@ +import { describe, it, expect } from 'vitest' +import { BaseFieldBuilder } from '@/builders/base' +import type { FieldBase } from '@/resources/types/field/base' +import FieldButton from '@/fields/core/FieldButton.vue' + +type TestField = FieldBase & { + type: 'test', + testKey: string +} + +class TestBaseFieldBuilder extends BaseFieldBuilder { + constructor(name: string, model: string) { + super('test', name, model) + } + + testKey (value: string): this { + this.data.testKey = value + return this + } + + getRequiredKeys(): (keyof TestField)[] { + const keys = super.getRequiredKeys() + return [ ...keys, 'testKey' ] + } + +} + +function testField (name: string, model: string): TestBaseFieldBuilder { + return new TestBaseFieldBuilder(name, model) +} + +function getBuilder (): TestBaseFieldBuilder { + return new TestBaseFieldBuilder('testField', 'testModel').testKey('test') +} + +describe('BaseFieldBuilder', () => { + + describe('constructor', () => { + + it('Should initialize with type, name and model', () => { + const field = testField('testField', 'testModel').testKey('test').build() + + expect(field.type).toBe('test') + expect(field.name).toBe('testField') + expect(field.testKey).toBe('test') + expect(field.model).toBe('testModel') + }) + + it('Should initialize data as partial object', () => { + const builder = testField('testField', 'testModel').testKey('test') + + expect(builder.__data__()).toEqual({ + type: 'test', + testKey: 'test', + name: 'testField', + model: 'testModel' + }) + }) + + }) + + describe('id()', () => { + + it('Should properly set the id property', () => { + const builder = getBuilder() + .id('testId') + + expect(builder.__data__().id).toBe('testId') + expect(builder.build().id).toBe('testId') + }) + + it('Should return builder instance', () => { + const builder = getBuilder().id('testId') + expect(builder).toBeInstanceOf(TestBaseFieldBuilder) + }) + + }) + + describe('label()', () => { + + it('Should properly set the label property', () => { + const builder = getBuilder().label('testLabel') + + expect(builder.__data__().label).toBe('testLabel') + expect(builder.build().label).toBe('testLabel') + }) + + it('Should return builder instance', () => { + const builder = getBuilder().label('testLabel') + expect(builder).toBeInstanceOf(TestBaseFieldBuilder) + }) + + }) + + describe('labelIcon()', () => { + + it('Should properly set the labelIcon property', () => { + const builder = getBuilder().labelIcon('testIcon') + + expect(builder.__data__().labelIcon).toBe('testIcon') + expect(builder.build().labelIcon).toBe('testIcon') + }) + + it('Should be able to set component instance as labelIcon', () => { + const builder = getBuilder().labelIcon(FieldButton) + + expect(builder.__data__().labelIcon).toBeDefined() + const field = builder.build() + // Verify that it is actually a component. + expect(field.labelIcon).toHaveProperty('__name', 'FieldButton') + expect(field.labelIcon).toBe(FieldButton) + }) + + it('Should be able to supply LabelDefinition as labelIcon', () => { + const builder = getBuilder().labelIcon({ + icon: FieldButton, + position: 'right' + }) + + expect(builder.__data__().labelIcon).toBeDefined() + expect(builder.__data__().labelIcon['icon']).toBe(FieldButton) + expect(builder.__data__().labelIcon['position']).toBe('right') + const field = builder.build() + // Verify that it is actually a component. + expect(field.labelIcon).toHaveProperty('icon') + expect(field.labelIcon).toHaveProperty('position') + expect(field.labelIcon['icon']).toBe(FieldButton) + }) + + it('Should return builder instance', () => { + const builder = getBuilder().labelIcon('testLabel') + expect(builder).toBeInstanceOf(TestBaseFieldBuilder) + }) + + }) + + describe('noLabel()', () => { + + it('Should properly set noLabel property', () => { + const builder = getBuilder().noLabel(true) + expect(builder.__data__().noLabel).toBe(true) + expect(builder.build().noLabel).toBe(true) + builder.noLabel(false) + expect(builder.__data__().noLabel).toBe(false) + + expect(builder.build().noLabel).toBe(false) + }) + + it('Should set noLabel property to true by default, if called', () => { + const builder = getBuilder() + expect(builder.__data__().noLabel).toBeUndefined() + + builder.noLabel() + expect(builder.__data__().noLabel).toBe(true) + expect(builder.build().noLabel).toBe(true) + }) + + it('Should return builder instance', () => { + const builder = getBuilder().noLabel() + expect(builder).toBeInstanceOf(TestBaseFieldBuilder) + }) + + }) + + describe('hint()', () => { + + it('Should properly set the hint property', () => { + const builder = getBuilder() + expect(builder.__data__().hint).toBeUndefined() + + builder.hint('testHint') + expect(builder.__data__().hint).toBe('testHint') + expect(builder.build().hint).toBe('testHint') + }) + + it('Should be able to set function as hint property', () => { + const hintFn = () => 'testHint' + const builder = getBuilder().hint(hintFn) + + expect(builder.__data__().hint).toBe(hintFn) + expect(builder.build().hint).toBe(hintFn) + }) + + it('Should return builder instance', () => { + const builder = getBuilder().hint('testHint') + expect(builder).toBeInstanceOf(TestBaseFieldBuilder) + }) + + }) + + describe('extend()', () => { + + it('Should extend field object with additional properties', () => { + const field = testField('testField', 'testModel').testKey('test').extend({ + testKey2: 2 + }).build() + + expect(field['testKey2']).not.toBeUndefined() + expect(field['testKey2']).toBe(2) + }) + + it('Should return builder instance', () => { + const builder = getBuilder().extend({}) + expect(builder).toBeInstanceOf(TestBaseFieldBuilder) + }) + + }) + + describe('build()', () => { + + it('Should contain all properties as set by the builder', () => { + const mockValidate = () => true + + const field = testField('testField', 'testModel') + .id('testId') + .testKey('test') + .hint('testHint') + .validator(mockValidate) + .validateOn('onBlur') + .onValidated(mockValidate) + .disabled() + .required() + .build() + + expect(field.hint).toBe('testHint') + expect(field.id).toBe('testId') + expect(field.testKey).toBe('test') + expect(field.validator).toBe(mockValidate) + expect(field.onValidated).toBe(mockValidate) + expect(field.validate).toBe('onBlur') + expect(field.disabled).toBe(true) + expect(field.required).toBe(true) + }) + + it('Should fail build when required fields are missing', () => { + const build = () => testField('testField', 'testModel').build() + expect(() => build()).toThrowError() + }) + + it('Should return regular JSON object', () => { + const field = getBuilder().build() + expect(field).not.toHaveProperty('__data__') + expect(field).not.toBeInstanceOf(TestBaseFieldBuilder) + }) + + }) + +}) From fa1f4958f65be66b814f8ef327672fc336989e13 Mon Sep 17 00:00:00 2001 From: kevinkosterr Date: Thu, 11 Dec 2025 19:40:48 +0100 Subject: [PATCH 02/15] chore: add linting to pull requests --- .github/actions/setup-pnpm/action.yml | 29 +++++++++++++++++++++ .github/workflows/pr-run-tests.yml | 36 --------------------------- .github/workflows/pr-validate.yml | 21 ++++++++++++++++ eslint.config.js | 2 +- package.json | 1 + 5 files changed, 52 insertions(+), 37 deletions(-) create mode 100644 .github/actions/setup-pnpm/action.yml delete mode 100644 .github/workflows/pr-run-tests.yml create mode 100644 .github/workflows/pr-validate.yml diff --git a/.github/actions/setup-pnpm/action.yml b/.github/actions/setup-pnpm/action.yml new file mode 100644 index 0000000..ee6d7b8 --- /dev/null +++ b/.github/actions/setup-pnpm/action.yml @@ -0,0 +1,29 @@ +name: Setup pnpm +description: Install pnpm, Node.js, and dependencies + +runs: + using: composite + steps: + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + - name: Setup pnpm cache + uses: actions/cache@v4 + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + - name: Install dependencies + shell: bash + run: pnpm install \ No newline at end of file diff --git a/.github/workflows/pr-run-tests.yml b/.github/workflows/pr-run-tests.yml deleted file mode 100644 index cd3505f..0000000 --- a/.github/workflows/pr-run-tests.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Validate tests for pull request - -on: - pull_request: - types: [ opened, synchronize, reopened ] - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'pnpm' - - name: Get pnpm store directory - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - name: Setup pnpm cache - uses: actions/cache@v4 - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - name: Install dependencies - run: pnpm install - - name: Run tests - run: pnpm run test \ No newline at end of file diff --git a/.github/workflows/pr-validate.yml b/.github/workflows/pr-validate.yml new file mode 100644 index 0000000..4fd03c2 --- /dev/null +++ b/.github/workflows/pr-validate.yml @@ -0,0 +1,21 @@ +name: Validate pull request + +on: + pull_request: + types: [ opened, synchronize, reopened ] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup-pnpm + - name: Run linter + run: pnpm run lint + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup-pnpm + - name: Run tests + run: pnpm run test \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js index 020c11b..4570b38 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -10,7 +10,7 @@ export default [ ...pluginVue.configs['flat/recommended'], ...typescriptEslint.configs['recommended'], { - ignores: [ 'dist' ] + ignores: [ 'dist', 'apps' ] }, { languageOptions: { diff --git a/package.json b/package.json index 795eef2..6acf903 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dev:sass": "sass --watch src/scss/themes:apps/playground/css/", "test": "vitest", "test:ui": "vitest --ui", + "lint": "eslint", "build": "vite build && sass src/scss/themes/:dist/themes/", "preview": "vite preview", "docs:dev": "vitepress dev apps/docs", From c835795973aeab9572d52aa29932306da9cbc82d Mon Sep 17 00:00:00 2001 From: kevinkosterr Date: Thu, 11 Dec 2025 19:45:48 +0100 Subject: [PATCH 03/15] chore: run build on pr --- .github/workflows/pr-validate.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-validate.yml b/.github/workflows/pr-validate.yml index 4fd03c2..3d79b46 100644 --- a/.github/workflows/pr-validate.yml +++ b/.github/workflows/pr-validate.yml @@ -18,4 +18,11 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-pnpm - name: Run tests - run: pnpm run test \ No newline at end of file + run: pnpm run test + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup-pnpm + - name: Build + run: pnpm run build From 517eb8f176f2a329927434ed56e670051d7264ea Mon Sep 17 00:00:00 2001 From: kevinkosterr Date: Thu, 11 Dec 2025 20:20:35 +0100 Subject: [PATCH 04/15] chore: run tsc on build --- package.json | 2 +- src/composables/useLabelIcon.ts | 8 ++++---- src/shims-vue.d.ts | 6 ++++++ 3 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 src/shims-vue.d.ts diff --git a/package.json b/package.json index 6acf903..d6cb2be 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "test": "vitest", "test:ui": "vitest --ui", "lint": "eslint", - "build": "vite build && sass src/scss/themes/:dist/themes/", + "build": "tsc --noEmit && vite build && sass src/scss/themes/:dist/themes/", "preview": "vite preview", "docs:dev": "vitepress dev apps/docs", "docs:build": "vitepress build apps/docs", diff --git a/src/composables/useLabelIcon.ts b/src/composables/useLabelIcon.ts index 50c9c61..9b9fcc0 100644 --- a/src/composables/useLabelIcon.ts +++ b/src/composables/useLabelIcon.ts @@ -1,24 +1,24 @@ -import { type ComputedRef, computed, type ComponentPublicInstance } from 'vue' +import { type ComputedRef, computed, type Component } from 'vue' import type { LabelIconDefinition } from '@/resources/types/field/base' /** * Simple composable that determines which icon to display and where. If there's no definition for an icon * @param iconDefinition - field schema icon definition. */ -export function useLabelIcon(iconDefinition: string | LabelIconDefinition | ComponentPublicInstance | undefined) { +export function useLabelIcon(iconDefinition: string | LabelIconDefinition | Component | undefined) { /** * The icon that should be displayed with a label. * If no icon is defined, return null. */ - const labelIcon: ComputedRef = computed(() => { + const labelIcon: ComputedRef = computed(() => { if (!iconDefinition) return null if (iconDefinition.hasOwnProperty('icon')) { return (iconDefinition as LabelIconDefinition).icon } - return (iconDefinition as ComponentPublicInstance | string) + return (iconDefinition as Component | string) }) /** diff --git a/src/shims-vue.d.ts b/src/shims-vue.d.ts new file mode 100644 index 0000000..789c833 --- /dev/null +++ b/src/shims-vue.d.ts @@ -0,0 +1,6 @@ +declare module '*.vue' { + import type { DefineComponent } from 'vue' + // eslint-disable-next-line @typescript-eslint/no-empty-object-type + const component: DefineComponent<{}, {}, any> + export default component +} \ No newline at end of file From 4dce17d71c8739a0ca78ace34586ed283e1059e1 Mon Sep 17 00:00:00 2001 From: kevinkosterr Date: Thu, 11 Dec 2025 21:08:55 +0100 Subject: [PATCH 05/15] WIP: test base builder separately, toRef() for schema builders --- src/builders/base.ts | 41 +++++++++--- src/builders/fields/TextFieldBuilder.ts | 2 +- src/builders/index.ts | 10 +-- src/builders/schema.ts | 5 ++ tests/builders/BaseBuilder.spec.ts | 54 ++++++++++++++++ tests/builders/BaseFieldBuilder.spec.ts | 83 +++++++------------------ 6 files changed, 120 insertions(+), 75 deletions(-) create mode 100644 tests/builders/BaseBuilder.spec.ts diff --git a/src/builders/base.ts b/src/builders/base.ts index b0d40fe..e848449 100644 --- a/src/builders/base.ts +++ b/src/builders/base.ts @@ -2,9 +2,11 @@ import type { Field } from '@/resources/types/field/fields' import type { FieldBase } from '@/resources/types/field/base' export interface IBaseBuilder { - build (): T - extend (properties: Record): this __data__ (): Partial + name (value: string): this + model(value: string): this + extend (properties: Record): this + build (): T } /** @@ -12,12 +14,11 @@ export interface IBaseBuilder { */ export abstract class BaseBuilder implements IBaseBuilder { - protected data: Partial + protected data: Partial = {} - protected constructor (type: string, name: string, model: string) { - this.data = { - type, name, model - } as Partial + protected constructor (type: string, model: string) { + this.data.type = type + this.data.model = model } __data__ (): Partial { return this.data } @@ -29,7 +30,25 @@ export abstract class BaseBuilder implements IBaseBuild * @protected */ protected getRequiredKeys (): (keyof T)[] { - return [ 'type', 'name', 'model' ] as (keyof T)[] + return [ 'type', 'model' ] as (keyof T)[] + } + + /** + * Set `name` property of the field. + * @param value + */ + name (value: string): this { + this.data.name = value + return this + } + + /** + * Set `model` property of the field. + * @param value + */ + model(value: string): this { + this.data.model = value + return this } /** @@ -47,10 +66,12 @@ export abstract class BaseBuilder implements IBaseBuild */ build (): T { const requiredKeys: (keyof T)[] = this.getRequiredKeys() - const missingKeys: (keyof T)[] = requiredKeys.filter((key: keyof T) => !(key in this.data)) + const missingKeys: (keyof T)[] = requiredKeys.filter((key: keyof T) => { + return !(key in this.data) && (this.data[key] === undefined || this.data[key] === null || this.data[key] === '') + }) if (missingKeys.length) { - throw new Error(`Failed to build field "${this.data.name}". Missing required keys: ${missingKeys.join(', ')}`) + throw new Error(`Failed to build field. Missing required keys: ${missingKeys.join(', ')}`) } return this.data as T diff --git a/src/builders/fields/TextFieldBuilder.ts b/src/builders/fields/TextFieldBuilder.ts index d3ce4f7..1fb708d 100644 --- a/src/builders/fields/TextFieldBuilder.ts +++ b/src/builders/fields/TextFieldBuilder.ts @@ -8,7 +8,7 @@ export interface ITextFieldBuilder extends IBaseFieldBuilder { export default class TextFieldBuilder extends BaseFieldBuilder implements ITextFieldBuilder { - constructor(name: string, model: string) { + constructor(name: string, model?: string) { super('input', name, model) this.data.inputType = 'text' } diff --git a/src/builders/index.ts b/src/builders/index.ts index f60171f..6d21d2b 100644 --- a/src/builders/index.ts +++ b/src/builders/index.ts @@ -89,14 +89,16 @@ export const f = { */ schema, /** + * + * * @example * // As builder - * f.text('fieldName', 'fieldModelKey').placeholder('Field placeholder') + * f.text('modelKey').placeholder('Field placeholder') * // As JSON - * f.text('fieldName', 'fieldModelKey').placeholder('Field placeholder').build() - * @param name + * f.text('modelKey').placeholder('Field placeholder').build() * @param model + * @returns A text field builder instance. */ - text: (name: string, model: string): TextFieldBuilder => new TextFieldBuilder(name, model) + text: (model?: string): TextFieldBuilder => new TextFieldBuilder(model) } diff --git a/src/builders/schema.ts b/src/builders/schema.ts index 365d9e3..ee61eb4 100644 --- a/src/builders/schema.ts +++ b/src/builders/schema.ts @@ -1,6 +1,7 @@ import { BaseFieldBuilder } from '@/builders/base' import { FormGeneratorSchema } from '@/resources/types/generic' import { GroupBuilder } from '@/builders/group' +import { type Ref, ref } from 'vue' /** * Form schema builder @@ -19,6 +20,10 @@ export class SchemaBuilder { this.groups = groups } + toRef (): Ref { + return ref(this.build()) + } + build (): FormGeneratorSchema { return { model: this.model, diff --git a/tests/builders/BaseBuilder.spec.ts b/tests/builders/BaseBuilder.spec.ts new file mode 100644 index 0000000..80102ab --- /dev/null +++ b/tests/builders/BaseBuilder.spec.ts @@ -0,0 +1,54 @@ +import { describe, it, expect } from 'vitest' +import { BaseBuilder } from '@/builders/base' +import { FieldBase } from '@/resources/types/field/base' + +type TestField = FieldBase & { [key: string]: any } +class TestBaseBuilder extends BaseBuilder { + constructor(model: string) { + super('test', model) + } +} + +function getBuilder () { + return new TestBaseBuilder('testName') +} + +describe('BaseBuilder', () => { + + describe('extend()', () => { + + it('Should extend field object with additional properties', () => { + const field = getBuilder().extend({ testKey2: 2 }).build() + + expect(field['testKey2']).not.toBeUndefined() + expect(field['testKey2']).toBe(2) + }) + + it('Should return builder instance', () => { + const builder = getBuilder().extend({}) + expect(builder).toBeInstanceOf(TestBaseBuilder) + }) + + }) + + describe('build()', () => { + + it('Should contain all properties as set by the builder', () => { + const field = getBuilder() + .name('name') + .model('model') + .build() + + expect(field.name).toBe('name') + expect(field.model).toBe('model') + }) + + it('Should return regular JSON object', () => { + const field = getBuilder().build() + expect(field).not.toHaveProperty('__data__') + expect(field).not.toBeInstanceOf(TestBaseBuilder) + }) + + }) + +}) diff --git a/tests/builders/BaseFieldBuilder.spec.ts b/tests/builders/BaseFieldBuilder.spec.ts index edd3560..03f3e68 100644 --- a/tests/builders/BaseFieldBuilder.spec.ts +++ b/tests/builders/BaseFieldBuilder.spec.ts @@ -9,8 +9,9 @@ type TestField = FieldBase & { } class TestBaseFieldBuilder extends BaseFieldBuilder { - constructor(name: string, model: string) { - super('test', name, model) + constructor(model: string, name?: string) { + super('test', model) + if (name) this.name(name) } testKey (value: string): this { @@ -26,7 +27,7 @@ class TestBaseFieldBuilder extends BaseFieldBuilder { } function testField (name: string, model: string): TestBaseFieldBuilder { - return new TestBaseFieldBuilder(name, model) + return new TestBaseFieldBuilder(model, name) } function getBuilder (): TestBaseFieldBuilder { @@ -46,6 +47,25 @@ describe('BaseFieldBuilder', () => { expect(field.model).toBe('testModel') }) + it('Should initialize without name and model', () => { + const builder = new TestBaseFieldBuilder('testName').testKey('test') + expect(builder.__data__().type).toBeDefined() + expect(builder.__data__().name).toBeUndefined() + + builder.name('testField').model('testModel') + expect(builder.__data__().name).toBe('testField') + expect(builder.__data__().model).toBe('testModel') + expect(() => { + builder.build() + }).not.toThrowError() + }) + + it('Should not initialize without model', () => { + expect(() => { + new TestBaseFieldBuilder().build() + }).toThrowError() + }) + it('Should initialize data as partial object', () => { const builder = testField('testField', 'testModel').testKey('test') @@ -188,61 +208,4 @@ describe('BaseFieldBuilder', () => { }) - describe('extend()', () => { - - it('Should extend field object with additional properties', () => { - const field = testField('testField', 'testModel').testKey('test').extend({ - testKey2: 2 - }).build() - - expect(field['testKey2']).not.toBeUndefined() - expect(field['testKey2']).toBe(2) - }) - - it('Should return builder instance', () => { - const builder = getBuilder().extend({}) - expect(builder).toBeInstanceOf(TestBaseFieldBuilder) - }) - - }) - - describe('build()', () => { - - it('Should contain all properties as set by the builder', () => { - const mockValidate = () => true - - const field = testField('testField', 'testModel') - .id('testId') - .testKey('test') - .hint('testHint') - .validator(mockValidate) - .validateOn('onBlur') - .onValidated(mockValidate) - .disabled() - .required() - .build() - - expect(field.hint).toBe('testHint') - expect(field.id).toBe('testId') - expect(field.testKey).toBe('test') - expect(field.validator).toBe(mockValidate) - expect(field.onValidated).toBe(mockValidate) - expect(field.validate).toBe('onBlur') - expect(field.disabled).toBe(true) - expect(field.required).toBe(true) - }) - - it('Should fail build when required fields are missing', () => { - const build = () => testField('testField', 'testModel').build() - expect(() => build()).toThrowError() - }) - - it('Should return regular JSON object', () => { - const field = getBuilder().build() - expect(field).not.toHaveProperty('__data__') - expect(field).not.toBeInstanceOf(TestBaseFieldBuilder) - }) - - }) - }) From 25aea8638263414fe6ef2761cb841adc763085f6 Mon Sep 17 00:00:00 2001 From: kevinkosterr Date: Thu, 11 Dec 2025 21:14:32 +0100 Subject: [PATCH 06/15] move all fields from core folder to root fields folder --- src/builders/fields/TextFieldBuilder.ts | 7 ++-- src/fields/{core => }/FieldButton.vue | 0 src/fields/{core => }/FieldCheckbox.vue | 0 src/fields/{core => }/FieldChecklist.vue | 0 src/fields/{core => }/FieldColor.vue | 0 src/fields/{core => }/FieldMask.vue | 0 src/fields/{core => }/FieldNumber.vue | 0 src/fields/{core => }/FieldObject.vue | 0 src/fields/{core => }/FieldPassword.vue | 0 src/fields/{core => }/FieldRadio.vue | 0 src/fields/{core => }/FieldReset.vue | 0 src/fields/{core => }/FieldSelect.vue | 0 src/fields/{core => }/FieldSelectNative.vue | 0 src/fields/{core => }/FieldSubmit.vue | 0 src/fields/{core => }/FieldSwitch.vue | 0 src/fields/{core => }/FieldText.vue | 0 src/fields/{core => }/FieldTextarea.vue | 0 src/fields/index.ts | 32 +++++++++---------- tests/builders/BaseFieldBuilder.spec.ts | 2 +- tests/components/FormGenerator.spec.ts | 8 ++--- tests/components/FormGroup.spec.ts | 2 +- tests/components/fields/FieldButton.spec.ts | 6 ++-- tests/components/fields/FieldCheckbox.spec.ts | 2 +- .../components/fields/FieldChecklist.spec.ts | 2 +- tests/components/fields/FieldColor.spec.ts | 2 +- tests/components/fields/FieldMask.spec.ts | 2 +- tests/components/fields/FieldNumber.spec.ts | 2 +- tests/components/fields/FieldObject.spec.ts | 6 ++-- tests/components/fields/FieldPassword.spec.ts | 2 +- tests/components/fields/FieldRadio.spec.ts | 2 +- tests/components/fields/FieldReset.spec.ts | 6 ++-- tests/components/fields/FieldSelect.spec.ts | 2 +- .../fields/FieldSelectNative.spec.ts | 2 +- tests/components/fields/FieldSubmit.spec.ts | 6 ++-- tests/components/fields/FieldSwitch.spec.ts | 2 +- tests/components/fields/FieldText.spec.ts | 2 +- tests/components/fields/FieldTextarea.spec.ts | 2 +- 37 files changed, 51 insertions(+), 48 deletions(-) rename src/fields/{core => }/FieldButton.vue (100%) rename src/fields/{core => }/FieldCheckbox.vue (100%) rename src/fields/{core => }/FieldChecklist.vue (100%) rename src/fields/{core => }/FieldColor.vue (100%) rename src/fields/{core => }/FieldMask.vue (100%) rename src/fields/{core => }/FieldNumber.vue (100%) rename src/fields/{core => }/FieldObject.vue (100%) rename src/fields/{core => }/FieldPassword.vue (100%) rename src/fields/{core => }/FieldRadio.vue (100%) rename src/fields/{core => }/FieldReset.vue (100%) rename src/fields/{core => }/FieldSelect.vue (100%) rename src/fields/{core => }/FieldSelectNative.vue (100%) rename src/fields/{core => }/FieldSubmit.vue (100%) rename src/fields/{core => }/FieldSwitch.vue (100%) rename src/fields/{core => }/FieldText.vue (100%) rename src/fields/{core => }/FieldTextarea.vue (100%) diff --git a/src/builders/fields/TextFieldBuilder.ts b/src/builders/fields/TextFieldBuilder.ts index 1fb708d..2b6a381 100644 --- a/src/builders/fields/TextFieldBuilder.ts +++ b/src/builders/fields/TextFieldBuilder.ts @@ -6,10 +6,13 @@ export interface ITextFieldBuilder extends IBaseFieldBuilder { autoComplete (value?: boolean): this } +/** + * Field builder for the text field. + */ export default class TextFieldBuilder extends BaseFieldBuilder implements ITextFieldBuilder { - constructor(name: string, model?: string) { - super('input', name, model) + constructor(model: string) { + super('input', model) this.data.inputType = 'text' } diff --git a/src/fields/core/FieldButton.vue b/src/fields/FieldButton.vue similarity index 100% rename from src/fields/core/FieldButton.vue rename to src/fields/FieldButton.vue diff --git a/src/fields/core/FieldCheckbox.vue b/src/fields/FieldCheckbox.vue similarity index 100% rename from src/fields/core/FieldCheckbox.vue rename to src/fields/FieldCheckbox.vue diff --git a/src/fields/core/FieldChecklist.vue b/src/fields/FieldChecklist.vue similarity index 100% rename from src/fields/core/FieldChecklist.vue rename to src/fields/FieldChecklist.vue diff --git a/src/fields/core/FieldColor.vue b/src/fields/FieldColor.vue similarity index 100% rename from src/fields/core/FieldColor.vue rename to src/fields/FieldColor.vue diff --git a/src/fields/core/FieldMask.vue b/src/fields/FieldMask.vue similarity index 100% rename from src/fields/core/FieldMask.vue rename to src/fields/FieldMask.vue diff --git a/src/fields/core/FieldNumber.vue b/src/fields/FieldNumber.vue similarity index 100% rename from src/fields/core/FieldNumber.vue rename to src/fields/FieldNumber.vue diff --git a/src/fields/core/FieldObject.vue b/src/fields/FieldObject.vue similarity index 100% rename from src/fields/core/FieldObject.vue rename to src/fields/FieldObject.vue diff --git a/src/fields/core/FieldPassword.vue b/src/fields/FieldPassword.vue similarity index 100% rename from src/fields/core/FieldPassword.vue rename to src/fields/FieldPassword.vue diff --git a/src/fields/core/FieldRadio.vue b/src/fields/FieldRadio.vue similarity index 100% rename from src/fields/core/FieldRadio.vue rename to src/fields/FieldRadio.vue diff --git a/src/fields/core/FieldReset.vue b/src/fields/FieldReset.vue similarity index 100% rename from src/fields/core/FieldReset.vue rename to src/fields/FieldReset.vue diff --git a/src/fields/core/FieldSelect.vue b/src/fields/FieldSelect.vue similarity index 100% rename from src/fields/core/FieldSelect.vue rename to src/fields/FieldSelect.vue diff --git a/src/fields/core/FieldSelectNative.vue b/src/fields/FieldSelectNative.vue similarity index 100% rename from src/fields/core/FieldSelectNative.vue rename to src/fields/FieldSelectNative.vue diff --git a/src/fields/core/FieldSubmit.vue b/src/fields/FieldSubmit.vue similarity index 100% rename from src/fields/core/FieldSubmit.vue rename to src/fields/FieldSubmit.vue diff --git a/src/fields/core/FieldSwitch.vue b/src/fields/FieldSwitch.vue similarity index 100% rename from src/fields/core/FieldSwitch.vue rename to src/fields/FieldSwitch.vue diff --git a/src/fields/core/FieldText.vue b/src/fields/FieldText.vue similarity index 100% rename from src/fields/core/FieldText.vue rename to src/fields/FieldText.vue diff --git a/src/fields/core/FieldTextarea.vue b/src/fields/FieldTextarea.vue similarity index 100% rename from src/fields/core/FieldTextarea.vue rename to src/fields/FieldTextarea.vue diff --git a/src/fields/index.ts b/src/fields/index.ts index a840079..7e558b0 100644 --- a/src/fields/index.ts +++ b/src/fields/index.ts @@ -1,23 +1,23 @@ import { App, Component } from 'vue' import type { FieldPluginOptions } from '@/resources/types/generic' -import FieldText from '@/fields/core/FieldText.vue' -import FieldPassword from '@/fields/core/FieldPassword.vue' -import FieldSelect from '@/fields/core/FieldSelect.vue' -import FieldSelectNative from '@/fields/core/FieldSelectNative.vue' -import FieldRadio from '@/fields/core/FieldRadio.vue' -import FieldColor from '@/fields/core/FieldColor.vue' -import FieldNumber from '@/fields/core/FieldNumber.vue' -import FieldSwitch from '@/fields/core/FieldSwitch.vue' -import FieldTextarea from '@/fields/core/FieldTextarea.vue' -import FieldMask from '@/fields/core/FieldMask.vue' -import FieldChecklist from '@/fields/core/FieldChecklist.vue' -import FieldCheckbox from '@/fields/core/FieldCheckbox.vue' -import FieldObject from '@/fields/core/FieldObject.vue' +import FieldText from '@/fields/FieldText.vue' +import FieldPassword from '@/fields/FieldPassword.vue' +import FieldSelect from '@/fields/FieldSelect.vue' +import FieldSelectNative from '@/fields/FieldSelectNative.vue' +import FieldRadio from '@/fields/FieldRadio.vue' +import FieldColor from '@/fields/FieldColor.vue' +import FieldNumber from '@/fields/FieldNumber.vue' +import FieldSwitch from '@/fields/FieldSwitch.vue' +import FieldTextarea from '@/fields/FieldTextarea.vue' +import FieldMask from '@/fields/FieldMask.vue' +import FieldChecklist from '@/fields/FieldChecklist.vue' +import FieldCheckbox from '@/fields/FieldCheckbox.vue' +import FieldObject from '@/fields/FieldObject.vue' -import FieldSubmit from '@/fields/core/FieldSubmit.vue' -import FieldReset from '@/fields/core/FieldReset.vue' -import FieldButton from '@/fields/core/FieldButton.vue' +import FieldSubmit from '@/fields/FieldSubmit.vue' +import FieldReset from '@/fields/FieldReset.vue' +import FieldButton from '@/fields/FieldButton.vue' const fieldComponents = { diff --git a/tests/builders/BaseFieldBuilder.spec.ts b/tests/builders/BaseFieldBuilder.spec.ts index 03f3e68..db8d14c 100644 --- a/tests/builders/BaseFieldBuilder.spec.ts +++ b/tests/builders/BaseFieldBuilder.spec.ts @@ -1,7 +1,7 @@ import { describe, it, expect } from 'vitest' import { BaseFieldBuilder } from '@/builders/base' import type { FieldBase } from '@/resources/types/field/base' -import FieldButton from '@/fields/core/FieldButton.vue' +import FieldButton from '@/fields/FieldButton.vue' type TestField = FieldBase & { type: 'test', diff --git a/tests/components/FormGenerator.spec.ts b/tests/components/FormGenerator.spec.ts index a9cac4e..d4df759 100644 --- a/tests/components/FormGenerator.spec.ts +++ b/tests/components/FormGenerator.spec.ts @@ -4,10 +4,10 @@ import { generateSchemaSingleField, clearEmittedEvents } from '@test/_resources/ import { mountFormGenerator } from '@test/_resources/utils.js' import FormGenerator from '@/FormGenerator.vue' -import FieldText from '@/fields/core/FieldText.vue' -import FieldTextarea from '@/fields/core/FieldTextarea.vue' -import FieldSubmit from '@/fields/core/FieldSubmit.vue' -import FieldReset from '@/fields/core/FieldReset.vue' +import FieldText from '@/fields/FieldText.vue' +import FieldTextarea from '@/fields/FieldTextarea.vue' +import FieldSubmit from '@/fields/FieldSubmit.vue' +import FieldReset from '@/fields/FieldReset.vue' beforeAll(() => { config.global.components = { FieldText, FieldTextarea, FieldSubmit, FieldReset } diff --git a/tests/components/FormGroup.spec.ts b/tests/components/FormGroup.spec.ts index 7295de0..9aaaeef 100644 --- a/tests/components/FormGroup.spec.ts +++ b/tests/components/FormGroup.spec.ts @@ -1,6 +1,6 @@ import { expect, it, describe, beforeAll } from 'vitest' import { mount, config } from '@vue/test-utils' -import FieldText from '@/fields/core/FieldText.vue' +import FieldText from '@/fields/FieldText.vue' import FormGroup from '@/FormGroup.vue' import { generateSchemaSingleField } from '@test/_resources/utils.js' diff --git a/tests/components/fields/FieldButton.spec.ts b/tests/components/fields/FieldButton.spec.ts index f98a9ec..1f83a50 100644 --- a/tests/components/fields/FieldButton.spec.ts +++ b/tests/components/fields/FieldButton.spec.ts @@ -2,9 +2,9 @@ import { mountFormGenerator, generatePropsSingleField } from '@test/_resources/u import { describe, it, expect, beforeAll } from 'vitest' import { mount, config } from '@vue/test-utils' -import FieldButton from '@/fields/core/FieldButton.vue' -import FieldPassword from '@/fields/core/FieldPassword.vue' -import FieldCheckbox from '@/fields/core/FieldCheckbox.vue' +import FieldButton from '@/fields/FieldButton.vue' +import FieldPassword from '@/fields/FieldPassword.vue' +import FieldCheckbox from '@/fields/FieldCheckbox.vue' beforeAll(() => { config.global.components = { FieldButton } diff --git a/tests/components/fields/FieldCheckbox.spec.ts b/tests/components/fields/FieldCheckbox.spec.ts index 0c8939d..54941b5 100644 --- a/tests/components/fields/FieldCheckbox.spec.ts +++ b/tests/components/fields/FieldCheckbox.spec.ts @@ -2,7 +2,7 @@ import { generatePropsSingleField, generateSchemaSingleField, mountFormGenerator import { describe, it, expect, beforeAll } from 'vitest' import { mount, config } from '@vue/test-utils' -import FieldCheckbox from '@/fields/core/FieldCheckbox.vue' +import FieldCheckbox from '@/fields/FieldCheckbox.vue' const form = generateSchemaSingleField( 'checkboxTestName', diff --git a/tests/components/fields/FieldChecklist.spec.ts b/tests/components/fields/FieldChecklist.spec.ts index 504a0c3..0114757 100644 --- a/tests/components/fields/FieldChecklist.spec.ts +++ b/tests/components/fields/FieldChecklist.spec.ts @@ -2,7 +2,7 @@ import { mountFormGenerator, generatePropsSingleField, generateSchemaSingleField import { it, describe, expect, beforeAll } from 'vitest' import { config, mount } from '@vue/test-utils' -import FieldChecklist from '@/fields/core/FieldChecklist.vue' +import FieldChecklist from '@/fields/FieldChecklist.vue' const form = generateSchemaSingleField( 'checklistTest', diff --git a/tests/components/fields/FieldColor.spec.ts b/tests/components/fields/FieldColor.spec.ts index 5d9d948..cdc9693 100644 --- a/tests/components/fields/FieldColor.spec.ts +++ b/tests/components/fields/FieldColor.spec.ts @@ -6,7 +6,7 @@ import { import { mount, config } from '@vue/test-utils' import { describe, it, expect, beforeAll } from 'vitest' -import FieldColor from '@/fields/core/FieldColor.vue' +import FieldColor from '@/fields/FieldColor.vue' const form = generateSchemaSingleField( 'testColor', diff --git a/tests/components/fields/FieldMask.spec.ts b/tests/components/fields/FieldMask.spec.ts index e5cc5a3..41d2ca2 100644 --- a/tests/components/fields/FieldMask.spec.ts +++ b/tests/components/fields/FieldMask.spec.ts @@ -6,7 +6,7 @@ import { import { mount, config } from '@vue/test-utils' import { describe, it, expect, beforeAll } from 'vitest' -import FieldMask from '@/fields/core/FieldMask.vue' +import FieldMask from '@/fields/FieldMask.vue' const form = generateSchemaSingleField( 'testMask', diff --git a/tests/components/fields/FieldNumber.spec.ts b/tests/components/fields/FieldNumber.spec.ts index 09ec1f8..6247413 100644 --- a/tests/components/fields/FieldNumber.spec.ts +++ b/tests/components/fields/FieldNumber.spec.ts @@ -2,7 +2,7 @@ import { describe, it, expect, beforeAll } from 'vitest' import { mountFormGenerator, generatePropsSingleField, generateSchemaSingleField } from '@test/_resources/utils.js' import { mount, config } from '@vue/test-utils' -import FieldNumber from '@/fields/core/FieldNumber.vue' +import FieldNumber from '@/fields/FieldNumber.vue' const form = generateSchemaSingleField( 'testNumber', diff --git a/tests/components/fields/FieldObject.spec.ts b/tests/components/fields/FieldObject.spec.ts index a28e0c2..eba46cf 100644 --- a/tests/components/fields/FieldObject.spec.ts +++ b/tests/components/fields/FieldObject.spec.ts @@ -2,9 +2,9 @@ import { generatePropsSingleField, mountFormGenerator } from '@test/_resources/u import { expect, it, describe, beforeAll } from 'vitest' import { config, mount } from '@vue/test-utils' -import FieldObject from '@/fields/core/FieldObject.vue' -import FieldNumber from '@/fields/core/FieldNumber.vue' -import FieldText from '@/fields/core/FieldText.vue' +import FieldObject from '@/fields/FieldObject.vue' +import FieldNumber from '@/fields/FieldNumber.vue' +import FieldText from '@/fields/FieldText.vue' import FormGenerator from '@/FormGenerator.vue' beforeAll(() => { diff --git a/tests/components/fields/FieldPassword.spec.ts b/tests/components/fields/FieldPassword.spec.ts index 7a4631b..f528973 100644 --- a/tests/components/fields/FieldPassword.spec.ts +++ b/tests/components/fields/FieldPassword.spec.ts @@ -3,7 +3,7 @@ import { describe, it, expect, beforeAll } from 'vitest' import { mount, config } from '@vue/test-utils' import validators from '@/validators' -import FieldPassword from '@/fields/core/FieldPassword.vue' +import FieldPassword from '@/fields/FieldPassword.vue' const form = generateSchemaSingleField( 'passwordTest', diff --git a/tests/components/fields/FieldRadio.spec.ts b/tests/components/fields/FieldRadio.spec.ts index e0f8e44..01efebf 100644 --- a/tests/components/fields/FieldRadio.spec.ts +++ b/tests/components/fields/FieldRadio.spec.ts @@ -2,7 +2,7 @@ import { mountFormGenerator, generatePropsSingleField, generateSchemaSingleField import { describe, it, expect, beforeAll } from 'vitest' import { mount, config } from '@vue/test-utils' -import FieldRadio from '@/fields/core/FieldRadio.vue' +import FieldRadio from '@/fields/FieldRadio.vue' const form = generateSchemaSingleField( 'radioTest', diff --git a/tests/components/fields/FieldReset.spec.ts b/tests/components/fields/FieldReset.spec.ts index 496107f..cd23b86 100644 --- a/tests/components/fields/FieldReset.spec.ts +++ b/tests/components/fields/FieldReset.spec.ts @@ -2,9 +2,9 @@ import { mountFormGenerator } from '@test/_resources/utils.js' import { describe, it, expect, beforeAll } from 'vitest' import { mount, config } from '@vue/test-utils' -import FieldReset from '@/fields/core/FieldReset.vue' -import FieldCheckbox from '@/fields/core/FieldCheckbox.vue' -import FieldPassword from '@/fields/core/FieldPassword.vue' +import FieldReset from '@/fields/FieldReset.vue' +import FieldCheckbox from '@/fields/FieldCheckbox.vue' +import FieldPassword from '@/fields/FieldPassword.vue' const form = { model: { diff --git a/tests/components/fields/FieldSelect.spec.ts b/tests/components/fields/FieldSelect.spec.ts index 9b20222..63a85bb 100644 --- a/tests/components/fields/FieldSelect.spec.ts +++ b/tests/components/fields/FieldSelect.spec.ts @@ -2,7 +2,7 @@ import { generateSchemaSingleField, generatePropsSingleField, mountFormGenerator import { mount, config } from '@vue/test-utils' import { describe, it, expect, beforeAll } from 'vitest' -import FieldSelect from '@/fields/core/FieldSelect.vue' +import FieldSelect from '@/fields/FieldSelect.vue' const form = generateSchemaSingleField( 'testSelect', diff --git a/tests/components/fields/FieldSelectNative.spec.ts b/tests/components/fields/FieldSelectNative.spec.ts index b839471..9577808 100644 --- a/tests/components/fields/FieldSelectNative.spec.ts +++ b/tests/components/fields/FieldSelectNative.spec.ts @@ -2,7 +2,7 @@ import { generateSchemaSingleField, generatePropsSingleField, mountFormGenerator import { mount, config } from '@vue/test-utils' import { describe, it, expect } from 'vitest' -import FieldSelect from '@/fields/core/FieldSelectNative.vue' +import FieldSelect from '@/fields/FieldSelectNative.vue' const form = generateSchemaSingleField( 'testSelect', diff --git a/tests/components/fields/FieldSubmit.spec.ts b/tests/components/fields/FieldSubmit.spec.ts index 8092fa5..9199709 100644 --- a/tests/components/fields/FieldSubmit.spec.ts +++ b/tests/components/fields/FieldSubmit.spec.ts @@ -2,9 +2,9 @@ import { mountFormGenerator } from '@test/_resources/utils.js' import { describe, it, expect } from 'vitest' import { mount, config } from '@vue/test-utils' -import FieldCheckbox from '@/fields/core/FieldCheckbox.vue' -import FieldPassword from '@/fields/core/FieldPassword.vue' -import FieldSubmit from '@/fields/core/FieldSubmit.vue' +import FieldCheckbox from '@/fields/FieldCheckbox.vue' +import FieldPassword from '@/fields/FieldPassword.vue' +import FieldSubmit from '@/fields/FieldSubmit.vue' const form = { model: { diff --git a/tests/components/fields/FieldSwitch.spec.ts b/tests/components/fields/FieldSwitch.spec.ts index c33b0d2..4939eff 100644 --- a/tests/components/fields/FieldSwitch.spec.ts +++ b/tests/components/fields/FieldSwitch.spec.ts @@ -2,7 +2,7 @@ import { generatePropsSingleField, generateSchemaSingleField, mountFormGenerator import { describe, it, expect } from 'vitest' import { mount, config } from '@vue/test-utils' -import FieldSwitch from '@/fields/core/FieldSwitch.vue' +import FieldSwitch from '@/fields/FieldSwitch.vue' const form = generateSchemaSingleField( 'switchTest', diff --git a/tests/components/fields/FieldText.spec.ts b/tests/components/fields/FieldText.spec.ts index 3a7e5f1..e918ba1 100644 --- a/tests/components/fields/FieldText.spec.ts +++ b/tests/components/fields/FieldText.spec.ts @@ -2,7 +2,7 @@ import { generateSchemaSingleField, generatePropsSingleField, mountFormGenerator import { mount, config } from '@vue/test-utils' import { describe, it, expect } from 'vitest' -import FieldText from '@/fields/core/FieldText.vue' +import FieldText from '@/fields/FieldText.vue' const form = generateSchemaSingleField( 'textTest', diff --git a/tests/components/fields/FieldTextarea.spec.ts b/tests/components/fields/FieldTextarea.spec.ts index b618b5f..e5a4602 100644 --- a/tests/components/fields/FieldTextarea.spec.ts +++ b/tests/components/fields/FieldTextarea.spec.ts @@ -2,7 +2,7 @@ import { generateSchemaSingleField, generatePropsSingleField, mountFormGenerator import { mount, config } from '@vue/test-utils' import { describe, it, expect } from 'vitest' -import FieldTextarea from '@/fields/core/FieldTextarea.vue' +import FieldTextarea from '@/fields/FieldTextarea.vue' const form = generateSchemaSingleField( 'textTest', From 592dd48680a170584325190a3f79cf76e40ef6e5 Mon Sep 17 00:00:00 2001 From: kevinkosterr Date: Thu, 11 Dec 2025 21:19:04 +0100 Subject: [PATCH 07/15] WIP: no optional model for text field builder helper --- src/builders/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/builders/index.ts b/src/builders/index.ts index 6d21d2b..c7a1096 100644 --- a/src/builders/index.ts +++ b/src/builders/index.ts @@ -99,6 +99,6 @@ export const f = { * @param model * @returns A text field builder instance. */ - text: (model?: string): TextFieldBuilder => new TextFieldBuilder(model) + text: (model: string): TextFieldBuilder => new TextFieldBuilder(model) } From e646ed20fe1beb03e29dc26ecc203a371ad8412d Mon Sep 17 00:00:00 2001 From: kevinkosterr Date: Thu, 11 Dec 2025 21:24:35 +0100 Subject: [PATCH 08/15] WIP: fix prop type labelIcon on FormLabel.vue --- src/components/FormLabel.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/FormLabel.vue b/src/components/FormLabel.vue index 6095de4..0c48348 100644 --- a/src/components/FormLabel.vue +++ b/src/components/FormLabel.vue @@ -13,11 +13,11 @@ @@ -23,11 +21,13 @@ export default {
- +
diff --git a/apps/playground/schema.example.js b/apps/playground/schema.example.js index de4e5ca..e0e22a1 100644 --- a/apps/playground/schema.example.js +++ b/apps/playground/schema.example.js @@ -1,40 +1,12 @@ -export default { - form: { - model: { - name: '', - surname: '', - terms: false - }, - schema: { - fields: [ - { - name: 'name', - label: 'Name', - type: 'input', - inputType: 'text', - model: 'name', - placeholder: 'Write name...', - readonly: false, - required: true - }, - { - name: 'surname', - label: 'Surname', - type: 'input', - inputType: 'text', - model: 'surname', - placeholder: 'Write surname...', - readonly: false, - required: true - }, - { - name: 'terms', - label: 'Accept terms and conditions', - type: 'input', - inputType: 'checkbox', - model: 'terms' - } - ] - } - } -} \ No newline at end of file +import { f } from '@/index.ts' + +export default f.schema( + { + name: '', + surname: '', + terms: false + }, + f.text('Name', 'name').label('Name').placeholder('Write name....').readonly(false).required(), + f.text('Surname', 'surname').label('Surname').placeholder('Write surname....').required(), + f.checkbox('Terms', 'terms').label('I accept terms and conditions') +).toRef() \ No newline at end of file diff --git a/src/builders/index.ts b/src/builders/index.ts index b655ba5..5c26a21 100644 --- a/src/builders/index.ts +++ b/src/builders/index.ts @@ -1,4 +1,4 @@ -import { GroupBuilder } from '@/builders/group' +import GroupBuilder from '@/builders/group' import { SchemaBuilder } from '@/builders/schema' import { BaseFieldBuilder } from '@/builders/base' diff --git a/src/index.ts b/src/index.ts index d22739c..5612b20 100644 --- a/src/index.ts +++ b/src/index.ts @@ -71,6 +71,8 @@ export { validators } +export { f } from '@/builders/index' + export type { FieldProps, FieldEmits, From dae24f29336791efdc44ad6b2348091832bd1bb8 Mon Sep 17 00:00:00 2001 From: kevinkosterr Date: Tue, 16 Dec 2025 17:46:04 +0100 Subject: [PATCH 12/15] WIP: rework schema builder, update pnpm lock, default value settings --- apps/playground/schema.example.js | 11 +- pnpm-lock.yaml | 723 ++++++++++--------- src/builders/base/BaseInput.ts | 4 +- src/builders/base/BaseOption.ts | 4 +- src/builders/base/index.ts | 36 +- src/builders/fields/CheckboxFieldBuilder.ts | 5 +- src/builders/fields/ChecklistFieldBuilder.ts | 5 +- src/builders/fields/TextFieldBuilder.ts | 5 +- src/builders/index.ts | 138 ++-- src/builders/schema.ts | 51 +- src/index.ts | 1 + tests/builders/BaseBuilder.spec.ts | 6 +- tests/builders/BaseFieldBuilder.spec.ts | 8 +- tests/builders/SchemaBuilder.spec.ts | 109 +-- tests/builders/TextFieldBuilder.spec.ts | 6 +- 15 files changed, 525 insertions(+), 587 deletions(-) diff --git a/apps/playground/schema.example.js b/apps/playground/schema.example.js index e0e22a1..5d159be 100644 --- a/apps/playground/schema.example.js +++ b/apps/playground/schema.example.js @@ -2,11 +2,8 @@ import { f } from '@/index.ts' export default f.schema( { - name: '', - surname: '', - terms: false - }, - f.text('Name', 'name').label('Name').placeholder('Write name....').readonly(false).required(), - f.text('Surname', 'surname').label('Surname').placeholder('Write surname....').required(), - f.checkbox('Terms', 'terms').label('I accept terms and conditions') + name: f.text('Name').label('Name').placeholder('Write name....').readonly(false).required(), + surname: f.text('Surname').label('Surname').placeholder('Write surname....').required(), + terms: f.checkbox('Terms').label('I accept terms and conditions') + } ).toRef() \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6924092..e96b4ab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,6 +12,11 @@ overrides: '@octokit/request@>=9.0.0-beta.1 <9.2.1': '>=9.2.1' esbuild@<=0.24.2: '>=0.25.0' form-data@>=4.0.0 <4.0.4: '>=4.0.4' + glob@>=10.2.0 <10.5.0: '>=10.5.0' + js-yaml@>=4.0.0 <4.1.1: '>=4.1.1' + mdast-util-to-hast@>=13.0.0 <13.2.1: '>=13.2.1' + vite@<=5.4.19: '>=5.4.20' + vite@>=5.2.6 <=5.4.20: '>=5.4.21' vitest@>=2.0.0 <2.1.9: '>=2.1.9' importers: @@ -39,7 +44,7 @@ importers: version: 8.18.0(eslint@9.16.0)(typescript@5.7.2) '@vitejs/plugin-vue': specifier: ^5.1.3 - version: 5.2.1(vite@5.4.19(sass@1.82.0)(terser@5.37.0))(vue@3.5.13(typescript@5.7.2)) + version: 5.2.1(vite@7.3.0(sass@1.82.0)(terser@5.37.0))(vue@3.5.13(typescript@5.7.2)) '@vitest/ui': specifier: ^3.2.4 version: 3.2.4(vitest@3.2.4) @@ -77,20 +82,20 @@ importers: specifier: ^8.13.0 version: 8.18.0(eslint@9.16.0)(typescript@5.7.2) vite: - specifier: ^5.4.19 - version: 5.4.19(sass@1.82.0)(terser@5.37.0) + specifier: '>=5.4.21' + version: 7.3.0(sass@1.82.0)(terser@5.37.0) vite-plugin-dts: specifier: ^4.3.0 - version: 4.3.0(rollup@4.28.1)(typescript@5.7.2)(vite@5.4.19(sass@1.82.0)(terser@5.37.0)) + version: 4.3.0(rollup@4.53.5)(typescript@5.7.2)(vite@7.3.0(sass@1.82.0)(terser@5.37.0)) vitepress: specifier: ^1.6.3 - version: 1.6.3(@algolia/client-search@5.25.0)(postcss@8.4.49)(sass@1.82.0)(search-insights@2.17.3)(terser@5.37.0)(typescript@5.7.2) + version: 1.6.3(@algolia/client-search@5.25.0)(postcss@8.5.6)(sass@1.82.0)(search-insights@2.17.3)(terser@5.37.0)(typescript@5.7.2) vitepress-plugin-group-icons: specifier: ^1.5.5 - version: 1.5.5(markdown-it@14.1.0)(vite@5.4.19(sass@1.82.0)(terser@5.37.0)) + version: 1.5.5(markdown-it@14.1.0)(vite@7.3.0(sass@1.82.0)(terser@5.37.0)) vitepress-plugin-tabs: specifier: ^0.7.1 - version: 0.7.1(vitepress@1.6.3(@algolia/client-search@5.25.0)(postcss@8.4.49)(sass@1.82.0)(search-insights@2.17.3)(terser@5.37.0)(typescript@5.7.2))(vue@3.5.13(typescript@5.7.2)) + version: 0.7.1(vitepress@1.6.3(@algolia/client-search@5.25.0)(postcss@8.5.6)(sass@1.82.0)(search-insights@2.17.3)(terser@5.37.0)(typescript@5.7.2))(vue@3.5.13(typescript@5.7.2)) vitest: specifier: ^3.2.4 version: 3.2.4(@vitest/ui@3.2.4)(jsdom@25.0.1)(sass@1.82.0)(terser@5.37.0) @@ -232,152 +237,158 @@ packages: search-insights: optional: true - '@esbuild/aix-ppc64@0.25.4': - resolution: {integrity: sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==} + '@esbuild/aix-ppc64@0.27.1': + resolution: {integrity: sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.4': - resolution: {integrity: sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==} + '@esbuild/android-arm64@0.27.1': + resolution: {integrity: sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.4': - resolution: {integrity: sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==} + '@esbuild/android-arm@0.27.1': + resolution: {integrity: sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.4': - resolution: {integrity: sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==} + '@esbuild/android-x64@0.27.1': + resolution: {integrity: sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.4': - resolution: {integrity: sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==} + '@esbuild/darwin-arm64@0.27.1': + resolution: {integrity: sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.4': - resolution: {integrity: sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==} + '@esbuild/darwin-x64@0.27.1': + resolution: {integrity: sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.4': - resolution: {integrity: sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==} + '@esbuild/freebsd-arm64@0.27.1': + resolution: {integrity: sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.4': - resolution: {integrity: sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==} + '@esbuild/freebsd-x64@0.27.1': + resolution: {integrity: sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.4': - resolution: {integrity: sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==} + '@esbuild/linux-arm64@0.27.1': + resolution: {integrity: sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.4': - resolution: {integrity: sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==} + '@esbuild/linux-arm@0.27.1': + resolution: {integrity: sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.4': - resolution: {integrity: sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==} + '@esbuild/linux-ia32@0.27.1': + resolution: {integrity: sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.4': - resolution: {integrity: sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==} + '@esbuild/linux-loong64@0.27.1': + resolution: {integrity: sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.4': - resolution: {integrity: sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==} + '@esbuild/linux-mips64el@0.27.1': + resolution: {integrity: sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.4': - resolution: {integrity: sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==} + '@esbuild/linux-ppc64@0.27.1': + resolution: {integrity: sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.4': - resolution: {integrity: sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==} + '@esbuild/linux-riscv64@0.27.1': + resolution: {integrity: sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.4': - resolution: {integrity: sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==} + '@esbuild/linux-s390x@0.27.1': + resolution: {integrity: sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.4': - resolution: {integrity: sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==} + '@esbuild/linux-x64@0.27.1': + resolution: {integrity: sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.4': - resolution: {integrity: sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==} + '@esbuild/netbsd-arm64@0.27.1': + resolution: {integrity: sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.4': - resolution: {integrity: sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==} + '@esbuild/netbsd-x64@0.27.1': + resolution: {integrity: sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.4': - resolution: {integrity: sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==} + '@esbuild/openbsd-arm64@0.27.1': + resolution: {integrity: sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.4': - resolution: {integrity: sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==} + '@esbuild/openbsd-x64@0.27.1': + resolution: {integrity: sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.25.4': - resolution: {integrity: sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==} + '@esbuild/openharmony-arm64@0.27.1': + resolution: {integrity: sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.1': + resolution: {integrity: sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.4': - resolution: {integrity: sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==} + '@esbuild/win32-arm64@0.27.1': + resolution: {integrity: sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.4': - resolution: {integrity: sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==} + '@esbuild/win32-ia32@0.27.1': + resolution: {integrity: sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.4': - resolution: {integrity: sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==} + '@esbuild/win32-x64@0.27.1': + resolution: {integrity: sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -455,9 +466,13 @@ packages: '@iconify/utils@2.3.0': resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==} - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} '@jridgewell/gen-mapping@0.3.5': resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} @@ -644,10 +659,6 @@ packages: resolution: {integrity: sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==} engines: {node: '>= 10.0.0'} - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - '@pnpm/config.env-replace@1.1.0': resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} engines: {node: '>=12.22.0'} @@ -672,98 +683,113 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.28.1': - resolution: {integrity: sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==} + '@rollup/rollup-android-arm-eabi@4.53.5': + resolution: {integrity: sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.28.1': - resolution: {integrity: sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==} + '@rollup/rollup-android-arm64@4.53.5': + resolution: {integrity: sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.28.1': - resolution: {integrity: sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==} + '@rollup/rollup-darwin-arm64@4.53.5': + resolution: {integrity: sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.28.1': - resolution: {integrity: sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==} + '@rollup/rollup-darwin-x64@4.53.5': + resolution: {integrity: sha512-YTbnsAaHo6VrAczISxgpTva8EkfQus0VPEVJCEaboHtZRIb6h6j0BNxRBOwnDciFTZLDPW5r+ZBmhL/+YpTZgA==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.28.1': - resolution: {integrity: sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==} + '@rollup/rollup-freebsd-arm64@4.53.5': + resolution: {integrity: sha512-1T8eY2J8rKJWzaznV7zedfdhD1BqVs1iqILhmHDq/bqCUZsrMt+j8VCTHhP0vdfbHK3e1IQ7VYx3jlKqwlf+vw==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.28.1': - resolution: {integrity: sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==} + '@rollup/rollup-freebsd-x64@4.53.5': + resolution: {integrity: sha512-sHTiuXyBJApxRn+VFMaw1U+Qsz4kcNlxQ742snICYPrY+DDL8/ZbaC4DVIB7vgZmp3jiDaKA0WpBdP0aqPJoBQ==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.28.1': - resolution: {integrity: sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==} + '@rollup/rollup-linux-arm-gnueabihf@4.53.5': + resolution: {integrity: sha512-dV3T9MyAf0w8zPVLVBptVlzaXxka6xg1f16VAQmjg+4KMSTWDvhimI/Y6mp8oHwNrmnmVl9XxJ/w/mO4uIQONA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.28.1': - resolution: {integrity: sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==} + '@rollup/rollup-linux-arm-musleabihf@4.53.5': + resolution: {integrity: sha512-wIGYC1x/hyjP+KAu9+ewDI+fi5XSNiUi9Bvg6KGAh2TsNMA3tSEs+Sh6jJ/r4BV/bx/CyWu2ue9kDnIdRyafcQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.28.1': - resolution: {integrity: sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==} + '@rollup/rollup-linux-arm64-gnu@4.53.5': + resolution: {integrity: sha512-Y+qVA0D9d0y2FRNiG9oM3Hut/DgODZbU9I8pLLPwAsU0tUKZ49cyV1tzmB/qRbSzGvY8lpgGkJuMyuhH7Ma+Vg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.28.1': - resolution: {integrity: sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==} + '@rollup/rollup-linux-arm64-musl@4.53.5': + resolution: {integrity: sha512-juaC4bEgJsyFVfqhtGLz8mbopaWD+WeSOYr5E16y+1of6KQjc0BpwZLuxkClqY1i8sco+MdyoXPNiCkQou09+g==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.28.1': - resolution: {integrity: sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==} + '@rollup/rollup-linux-loong64-gnu@4.53.5': + resolution: {integrity: sha512-rIEC0hZ17A42iXtHX+EPJVL/CakHo+tT7W0pbzdAGuWOt2jxDFh7A/lRhsNHBcqL4T36+UiAgwO8pbmn3dE8wA==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.28.1': - resolution: {integrity: sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==} + '@rollup/rollup-linux-ppc64-gnu@4.53.5': + resolution: {integrity: sha512-T7l409NhUE552RcAOcmJHj3xyZ2h7vMWzcwQI0hvn5tqHh3oSoclf9WgTl+0QqffWFG8MEVZZP1/OBglKZx52Q==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.28.1': - resolution: {integrity: sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==} + '@rollup/rollup-linux-riscv64-gnu@4.53.5': + resolution: {integrity: sha512-7OK5/GhxbnrMcxIFoYfhV/TkknarkYC1hqUw1wU2xUN3TVRLNT5FmBv4KkheSG2xZ6IEbRAhTooTV2+R5Tk0lQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.28.1': - resolution: {integrity: sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==} + '@rollup/rollup-linux-riscv64-musl@4.53.5': + resolution: {integrity: sha512-GwuDBE/PsXaTa76lO5eLJTyr2k8QkPipAyOrs4V/KJufHCZBJ495VCGJol35grx9xryk4V+2zd3Ri+3v7NPh+w==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.53.5': + resolution: {integrity: sha512-IAE1Ziyr1qNfnmiQLHBURAD+eh/zH1pIeJjeShleII7Vj8kyEm2PF77o+lf3WTHDpNJcu4IXJxNO0Zluro8bOw==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.28.1': - resolution: {integrity: sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==} + '@rollup/rollup-linux-x64-gnu@4.53.5': + resolution: {integrity: sha512-Pg6E+oP7GvZ4XwgRJBuSXZjcqpIW3yCBhK4BcsANvb47qMvAbCjR6E+1a/U2WXz1JJxp9/4Dno3/iSJLcm5auw==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.28.1': - resolution: {integrity: sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==} + '@rollup/rollup-linux-x64-musl@4.53.5': + resolution: {integrity: sha512-txGtluxDKTxaMDzUduGP0wdfng24y1rygUMnmlUJ88fzCCULCLn7oE5kb2+tRB+MWq1QDZT6ObT5RrR8HFRKqg==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.28.1': - resolution: {integrity: sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==} + '@rollup/rollup-openharmony-arm64@4.53.5': + resolution: {integrity: sha512-3DFiLPnTxiOQV993fMc+KO8zXHTcIjgaInrqlG8zDp1TlhYl6WgrOHuJkJQ6M8zHEcntSJsUp1XFZSY8C1DYbg==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.53.5': + resolution: {integrity: sha512-nggc/wPpNTgjGg75hu+Q/3i32R00Lq1B6N1DO7MCU340MRKL3WZJMjA9U4K4gzy3dkZPXm9E1Nc81FItBVGRlA==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.28.1': - resolution: {integrity: sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==} + '@rollup/rollup-win32-ia32-msvc@4.53.5': + resolution: {integrity: sha512-U/54pTbdQpPLBdEzCT6NBCFAfSZMvmjr0twhnD9f4EIvlm9wy3jjQ38yQj1AGznrNO65EWQMgm/QUjuIVrYF9w==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.28.1': - resolution: {integrity: sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==} + '@rollup/rollup-win32-x64-gnu@4.53.5': + resolution: {integrity: sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.53.5': + resolution: {integrity: sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ==} cpu: [x64] os: [win32] @@ -874,6 +900,9 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -958,7 +987,7 @@ packages: resolution: {integrity: sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==} engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: - vite: ^5.0.0 || ^6.0.0 + vite: '>=5.4.21' vue: ^3.2.25 '@vitest/expect@3.2.4': @@ -968,7 +997,7 @@ packages: resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} peerDependencies: msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + vite: '>=5.4.21' peerDependenciesMeta: msw: optional: true @@ -1190,10 +1219,6 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} - any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -1495,9 +1520,6 @@ packages: duplexer2@0.1.4: resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - editorconfig@1.0.4: resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==} engines: {node: '>=14'} @@ -1509,9 +1531,6 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - emojilib@2.4.0: resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} @@ -1553,8 +1572,8 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} - esbuild@0.25.4: - resolution: {integrity: sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==} + esbuild@0.27.1: + resolution: {integrity: sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==} engines: {node: '>=18'} hasBin: true @@ -1681,6 +1700,15 @@ packages: picomatch: optional: true + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} @@ -1729,10 +1757,6 @@ packages: focus-trap@7.6.4: resolution: {integrity: sha512-xx560wGBk7seZ6y933idtjJQc1l+ck+pI3sKvhKozdBV1dRZoKhkW5xoCaFv9tQiX5RH1xfSxjuNu6g+lmN/gw==} - foreground-child@3.3.0: - resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} - engines: {node: '>=14'} - form-data@4.0.4: resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} @@ -1799,9 +1823,9 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} - hasBin: true + glob@13.0.0: + resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==} + engines: {node: 20 || >=22} globals@13.24.0: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} @@ -2019,9 +2043,6 @@ packages: resolution: {integrity: sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==} engines: {node: ^18.17 || >=20.6.1} - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - java-properties@1.0.2: resolution: {integrity: sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==} engines: {node: '>= 0.6.0'} @@ -2044,8 +2065,8 @@ packages: js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true jsdom@25.0.1: @@ -2147,6 +2168,10 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.2.4: + resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} + engines: {node: 20 || >=22} + lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -2182,8 +2207,8 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - mdast-util-to-hast@13.2.0: - resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} mdurl@2.0.0: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} @@ -2235,6 +2260,10 @@ packages: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} + minimatch@10.1.1: + resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} + engines: {node: 20 || >=22} + minimatch@3.0.8: resolution: {integrity: sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==} @@ -2281,6 +2310,11 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + nanoid@3.3.8: resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -2458,9 +2492,6 @@ packages: resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} engines: {node: '>=4'} - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - package-manager-detector@1.3.0: resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} @@ -2518,9 +2549,9 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} + path-scurry@2.0.1: + resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} + engines: {node: 20 || >=22} path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} @@ -2554,6 +2585,10 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + pify@3.0.0: resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} engines: {node: '>=4'} @@ -2579,6 +2614,10 @@ packages: resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + preact@10.26.6: resolution: {integrity: sha512-5SRRBinwpwkaD+OqlBDeITlRgvd8I8QlxHJw9AxSdMNV6O+LodN9nUyYGpSF7sadHjs6RzeFShMexC6DbtWr9g==} @@ -2672,8 +2711,8 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rollup@4.28.1: - resolution: {integrity: sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==} + rollup@4.53.5: + resolution: {integrity: sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2817,10 +2856,6 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -2831,10 +2866,6 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} - strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} @@ -2935,6 +2966,10 @@ packages: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + tinypool@1.1.1: resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} @@ -3102,27 +3137,32 @@ packages: engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: typescript: '*' - vite: '*' + vite: '>=5.4.21' peerDependenciesMeta: vite: optional: true - vite@5.4.19: - resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==} - engines: {node: ^18.0.0 || >=20.0.0} + vite@7.3.0: + resolution: {integrity: sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 peerDependenciesMeta: '@types/node': optional: true + jiti: + optional: true less: optional: true lightningcss: @@ -3137,12 +3177,16 @@ packages: optional: true terser: optional: true + tsx: + optional: true + yaml: + optional: true vitepress-plugin-group-icons@1.5.5: resolution: {integrity: sha512-eVnBL3lVOYxByQg5xo44QZtGPv41JyxWI7YxuwrGcNUU+W8MMIjb9XlivBXb3W8CosFllJlLGiqNCBTnFZHFTA==} peerDependencies: markdown-it: '>=14' - vite: '>=3' + vite: '>=5.4.21' vitepress-plugin-tabs@0.7.1: resolution: {integrity: sha512-jxJvsicxnMSIYX9b8mAFLD2nwyKUcMO10dEt4nDSbinZhM8cGvAmMFOHPdf6TBX6gYZRl+/++/iYHtoM14fERQ==} @@ -3256,10 +3300,6 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - ws@8.18.0: resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} engines: {node: '>=10.0.0'} @@ -3481,79 +3521,82 @@ snapshots: transitivePeerDependencies: - '@algolia/client-search' - '@esbuild/aix-ppc64@0.25.4': + '@esbuild/aix-ppc64@0.27.1': optional: true - '@esbuild/android-arm64@0.25.4': + '@esbuild/android-arm64@0.27.1': optional: true - '@esbuild/android-arm@0.25.4': + '@esbuild/android-arm@0.27.1': optional: true - '@esbuild/android-x64@0.25.4': + '@esbuild/android-x64@0.27.1': optional: true - '@esbuild/darwin-arm64@0.25.4': + '@esbuild/darwin-arm64@0.27.1': optional: true - '@esbuild/darwin-x64@0.25.4': + '@esbuild/darwin-x64@0.27.1': optional: true - '@esbuild/freebsd-arm64@0.25.4': + '@esbuild/freebsd-arm64@0.27.1': optional: true - '@esbuild/freebsd-x64@0.25.4': + '@esbuild/freebsd-x64@0.27.1': optional: true - '@esbuild/linux-arm64@0.25.4': + '@esbuild/linux-arm64@0.27.1': optional: true - '@esbuild/linux-arm@0.25.4': + '@esbuild/linux-arm@0.27.1': optional: true - '@esbuild/linux-ia32@0.25.4': + '@esbuild/linux-ia32@0.27.1': optional: true - '@esbuild/linux-loong64@0.25.4': + '@esbuild/linux-loong64@0.27.1': optional: true - '@esbuild/linux-mips64el@0.25.4': + '@esbuild/linux-mips64el@0.27.1': optional: true - '@esbuild/linux-ppc64@0.25.4': + '@esbuild/linux-ppc64@0.27.1': optional: true - '@esbuild/linux-riscv64@0.25.4': + '@esbuild/linux-riscv64@0.27.1': optional: true - '@esbuild/linux-s390x@0.25.4': + '@esbuild/linux-s390x@0.27.1': optional: true - '@esbuild/linux-x64@0.25.4': + '@esbuild/linux-x64@0.27.1': optional: true - '@esbuild/netbsd-arm64@0.25.4': + '@esbuild/netbsd-arm64@0.27.1': optional: true - '@esbuild/netbsd-x64@0.25.4': + '@esbuild/netbsd-x64@0.27.1': optional: true - '@esbuild/openbsd-arm64@0.25.4': + '@esbuild/openbsd-arm64@0.27.1': optional: true - '@esbuild/openbsd-x64@0.25.4': + '@esbuild/openbsd-x64@0.27.1': optional: true - '@esbuild/sunos-x64@0.25.4': + '@esbuild/openharmony-arm64@0.27.1': optional: true - '@esbuild/win32-arm64@0.25.4': + '@esbuild/sunos-x64@0.27.1': optional: true - '@esbuild/win32-ia32@0.25.4': + '@esbuild/win32-arm64@0.27.1': optional: true - '@esbuild/win32-x64@0.25.4': + '@esbuild/win32-ia32@0.27.1': + optional: true + + '@esbuild/win32-x64@0.27.1': optional: true '@eslint-community/eslint-utils@4.4.1(eslint@9.16.0)': @@ -3587,7 +3630,7 @@ snapshots: globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.0 - js-yaml: 4.1.0 + js-yaml: 4.1.1 minimatch: 3.1.2 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -3642,14 +3685,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@isaacs/cliui@8.0.2': + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/balanced-match': 4.0.1 '@jridgewell/gen-mapping@0.3.5': dependencies: @@ -3848,9 +3888,6 @@ snapshots: '@parcel/watcher-win32-x64': 2.5.0 optional: true - '@pkgjs/parseargs@0.11.0': - optional: true - '@pnpm/config.env-replace@1.1.0': {} '@pnpm/network.ca-file@1.0.2': @@ -3865,69 +3902,78 @@ snapshots: '@polka/url@1.0.0-next.29': {} - '@rollup/pluginutils@5.1.3(rollup@4.28.1)': + '@rollup/pluginutils@5.1.3(rollup@4.53.5)': dependencies: '@types/estree': 1.0.6 estree-walker: 2.0.2 picomatch: 4.0.2 optionalDependencies: - rollup: 4.28.1 + rollup: 4.53.5 + + '@rollup/rollup-android-arm-eabi@4.53.5': + optional: true + + '@rollup/rollup-android-arm64@4.53.5': + optional: true + + '@rollup/rollup-darwin-arm64@4.53.5': + optional: true - '@rollup/rollup-android-arm-eabi@4.28.1': + '@rollup/rollup-darwin-x64@4.53.5': optional: true - '@rollup/rollup-android-arm64@4.28.1': + '@rollup/rollup-freebsd-arm64@4.53.5': optional: true - '@rollup/rollup-darwin-arm64@4.28.1': + '@rollup/rollup-freebsd-x64@4.53.5': optional: true - '@rollup/rollup-darwin-x64@4.28.1': + '@rollup/rollup-linux-arm-gnueabihf@4.53.5': optional: true - '@rollup/rollup-freebsd-arm64@4.28.1': + '@rollup/rollup-linux-arm-musleabihf@4.53.5': optional: true - '@rollup/rollup-freebsd-x64@4.28.1': + '@rollup/rollup-linux-arm64-gnu@4.53.5': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.28.1': + '@rollup/rollup-linux-arm64-musl@4.53.5': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.28.1': + '@rollup/rollup-linux-loong64-gnu@4.53.5': optional: true - '@rollup/rollup-linux-arm64-gnu@4.28.1': + '@rollup/rollup-linux-ppc64-gnu@4.53.5': optional: true - '@rollup/rollup-linux-arm64-musl@4.28.1': + '@rollup/rollup-linux-riscv64-gnu@4.53.5': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.28.1': + '@rollup/rollup-linux-riscv64-musl@4.53.5': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.28.1': + '@rollup/rollup-linux-s390x-gnu@4.53.5': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.28.1': + '@rollup/rollup-linux-x64-gnu@4.53.5': optional: true - '@rollup/rollup-linux-s390x-gnu@4.28.1': + '@rollup/rollup-linux-x64-musl@4.53.5': optional: true - '@rollup/rollup-linux-x64-gnu@4.28.1': + '@rollup/rollup-openharmony-arm64@4.53.5': optional: true - '@rollup/rollup-linux-x64-musl@4.28.1': + '@rollup/rollup-win32-arm64-msvc@4.53.5': optional: true - '@rollup/rollup-win32-arm64-msvc@4.28.1': + '@rollup/rollup-win32-ia32-msvc@4.53.5': optional: true - '@rollup/rollup-win32-ia32-msvc@4.28.1': + '@rollup/rollup-win32-x64-gnu@4.53.5': optional: true - '@rollup/rollup-win32-x64-msvc@4.28.1': + '@rollup/rollup-win32-x64-msvc@4.53.5': optional: true '@rushstack/node-core-library@5.10.0': @@ -4101,6 +4147,8 @@ snapshots: '@types/estree@1.0.6': {} + '@types/estree@1.0.8': {} + '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 @@ -4207,9 +4255,9 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-vue@5.2.1(vite@5.4.19(sass@1.82.0)(terser@5.37.0))(vue@3.5.13(typescript@5.7.2))': + '@vitejs/plugin-vue@5.2.1(vite@7.3.0(sass@1.82.0)(terser@5.37.0))(vue@3.5.13(typescript@5.7.2))': dependencies: - vite: 5.4.19(sass@1.82.0)(terser@5.37.0) + vite: 7.3.0(sass@1.82.0)(terser@5.37.0) vue: 3.5.13(typescript@5.7.2) '@vitest/expect@3.2.4': @@ -4220,13 +4268,13 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@5.4.19(sass@1.82.0)(terser@5.37.0))': + '@vitest/mocker@3.2.4(vite@7.3.0(sass@1.82.0)(terser@5.37.0))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 5.4.19(sass@1.82.0)(terser@5.37.0) + vite: 7.3.0(sass@1.82.0)(terser@5.37.0) '@vitest/pretty-format@3.2.4': dependencies: @@ -4488,8 +4536,6 @@ snapshots: dependencies: color-convert: 2.0.1 - ansi-styles@6.2.1: {} - any-promise@1.3.0: {} argparse@1.0.10: @@ -4684,7 +4730,7 @@ snapshots: dependencies: env-paths: 2.2.1 import-fresh: 3.3.0 - js-yaml: 4.1.0 + js-yaml: 4.1.1 parse-json: 5.2.0 optionalDependencies: typescript: 5.7.2 @@ -4761,8 +4807,6 @@ snapshots: dependencies: readable-stream: 2.3.8 - eastasianwidth@0.2.0: {} - editorconfig@1.0.4: dependencies: '@one-ini/wasm': 0.1.1 @@ -4774,8 +4818,6 @@ snapshots: emoji-regex@8.0.0: {} - emoji-regex@9.2.2: {} - emojilib@2.4.0: {} entities@4.5.0: {} @@ -4810,33 +4852,34 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 - esbuild@0.25.4: + esbuild@0.27.1: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.4 - '@esbuild/android-arm': 0.25.4 - '@esbuild/android-arm64': 0.25.4 - '@esbuild/android-x64': 0.25.4 - '@esbuild/darwin-arm64': 0.25.4 - '@esbuild/darwin-x64': 0.25.4 - '@esbuild/freebsd-arm64': 0.25.4 - '@esbuild/freebsd-x64': 0.25.4 - '@esbuild/linux-arm': 0.25.4 - '@esbuild/linux-arm64': 0.25.4 - '@esbuild/linux-ia32': 0.25.4 - '@esbuild/linux-loong64': 0.25.4 - '@esbuild/linux-mips64el': 0.25.4 - '@esbuild/linux-ppc64': 0.25.4 - '@esbuild/linux-riscv64': 0.25.4 - '@esbuild/linux-s390x': 0.25.4 - '@esbuild/linux-x64': 0.25.4 - '@esbuild/netbsd-arm64': 0.25.4 - '@esbuild/netbsd-x64': 0.25.4 - '@esbuild/openbsd-arm64': 0.25.4 - '@esbuild/openbsd-x64': 0.25.4 - '@esbuild/sunos-x64': 0.25.4 - '@esbuild/win32-arm64': 0.25.4 - '@esbuild/win32-ia32': 0.25.4 - '@esbuild/win32-x64': 0.25.4 + '@esbuild/aix-ppc64': 0.27.1 + '@esbuild/android-arm': 0.27.1 + '@esbuild/android-arm64': 0.27.1 + '@esbuild/android-x64': 0.27.1 + '@esbuild/darwin-arm64': 0.27.1 + '@esbuild/darwin-x64': 0.27.1 + '@esbuild/freebsd-arm64': 0.27.1 + '@esbuild/freebsd-x64': 0.27.1 + '@esbuild/linux-arm': 0.27.1 + '@esbuild/linux-arm64': 0.27.1 + '@esbuild/linux-ia32': 0.27.1 + '@esbuild/linux-loong64': 0.27.1 + '@esbuild/linux-mips64el': 0.27.1 + '@esbuild/linux-ppc64': 0.27.1 + '@esbuild/linux-riscv64': 0.27.1 + '@esbuild/linux-s390x': 0.27.1 + '@esbuild/linux-x64': 0.27.1 + '@esbuild/netbsd-arm64': 0.27.1 + '@esbuild/netbsd-x64': 0.27.1 + '@esbuild/openbsd-arm64': 0.27.1 + '@esbuild/openbsd-x64': 0.27.1 + '@esbuild/openharmony-arm64': 0.27.1 + '@esbuild/sunos-x64': 0.27.1 + '@esbuild/win32-arm64': 0.27.1 + '@esbuild/win32-ia32': 0.27.1 + '@esbuild/win32-x64': 0.27.1 escalade@3.2.0: {} @@ -5000,6 +5043,10 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + fflate@0.8.2: {} figures@2.0.0: @@ -5047,11 +5094,6 @@ snapshots: dependencies: tabbable: 6.2.0 - foreground-child@3.3.0: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - form-data@4.0.4: dependencies: asynckit: 0.4.0 @@ -5132,14 +5174,11 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@10.4.5: + glob@13.0.0: dependencies: - foreground-child: 3.3.0 - jackspeak: 3.4.3 - minimatch: 9.0.5 + minimatch: 10.1.1 minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 + path-scurry: 2.0.1 globals@13.24.0: dependencies: @@ -5201,7 +5240,7 @@ snapshots: comma-separated-tokens: 2.0.3 hast-util-whitespace: 3.0.0 html-void-elements: 3.0.0 - mdast-util-to-hast: 13.2.0 + mdast-util-to-hast: 13.2.1 property-information: 7.1.0 space-separated-tokens: 2.0.2 stringify-entities: 4.0.4 @@ -5332,12 +5371,6 @@ snapshots: lodash.isstring: 4.0.1 lodash.uniqby: 4.7.0 - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - java-properties@1.0.2: {} jju@1.4.0: {} @@ -5346,7 +5379,7 @@ snapshots: dependencies: config-chain: 1.1.13 editorconfig: 1.0.4 - glob: 10.4.5 + glob: 13.0.0 js-cookie: 3.0.5 nopt: 7.2.1 @@ -5356,7 +5389,7 @@ snapshots: js-tokens@9.0.1: {} - js-yaml@4.1.0: + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -5474,6 +5507,8 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@11.2.4: {} + lru-cache@6.0.0: dependencies: yallist: 4.0.0 @@ -5514,7 +5549,7 @@ snapshots: math-intrinsics@1.1.0: {} - mdast-util-to-hast@13.2.0: + mdast-util-to-hast@13.2.1: dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 @@ -5566,6 +5601,10 @@ snapshots: mimic-fn@4.0.0: {} + minimatch@10.1.1: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + minimatch@3.0.8: dependencies: brace-expansion: 1.1.12 @@ -5616,6 +5655,8 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 + nanoid@3.3.11: {} + nanoid@3.3.8: {} natural-compare@1.4.0: {} @@ -5714,8 +5755,6 @@ snapshots: p-try@1.0.0: {} - package-json-from-dist@1.0.1: {} - package-manager-detector@1.3.0: {} parent-module@1.0.1: @@ -5766,9 +5805,9 @@ snapshots: path-parse@1.0.7: {} - path-scurry@1.11.1: + path-scurry@2.0.1: dependencies: - lru-cache: 10.4.3 + lru-cache: 11.2.4 minipass: 7.1.2 path-type@4.0.0: {} @@ -5789,6 +5828,8 @@ snapshots: picomatch@4.0.2: {} + picomatch@4.0.3: {} + pify@3.0.0: {} pkg-conf@2.1.0: @@ -5825,6 +5866,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + preact@10.26.6: {} prelude-ls@1.2.1: {} @@ -5912,29 +5959,32 @@ snapshots: rfdc@1.4.1: {} - rollup@4.28.1: + rollup@4.53.5: dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.28.1 - '@rollup/rollup-android-arm64': 4.28.1 - '@rollup/rollup-darwin-arm64': 4.28.1 - '@rollup/rollup-darwin-x64': 4.28.1 - '@rollup/rollup-freebsd-arm64': 4.28.1 - '@rollup/rollup-freebsd-x64': 4.28.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.28.1 - '@rollup/rollup-linux-arm-musleabihf': 4.28.1 - '@rollup/rollup-linux-arm64-gnu': 4.28.1 - '@rollup/rollup-linux-arm64-musl': 4.28.1 - '@rollup/rollup-linux-loongarch64-gnu': 4.28.1 - '@rollup/rollup-linux-powerpc64le-gnu': 4.28.1 - '@rollup/rollup-linux-riscv64-gnu': 4.28.1 - '@rollup/rollup-linux-s390x-gnu': 4.28.1 - '@rollup/rollup-linux-x64-gnu': 4.28.1 - '@rollup/rollup-linux-x64-musl': 4.28.1 - '@rollup/rollup-win32-arm64-msvc': 4.28.1 - '@rollup/rollup-win32-ia32-msvc': 4.28.1 - '@rollup/rollup-win32-x64-msvc': 4.28.1 + '@rollup/rollup-android-arm-eabi': 4.53.5 + '@rollup/rollup-android-arm64': 4.53.5 + '@rollup/rollup-darwin-arm64': 4.53.5 + '@rollup/rollup-darwin-x64': 4.53.5 + '@rollup/rollup-freebsd-arm64': 4.53.5 + '@rollup/rollup-freebsd-x64': 4.53.5 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.5 + '@rollup/rollup-linux-arm-musleabihf': 4.53.5 + '@rollup/rollup-linux-arm64-gnu': 4.53.5 + '@rollup/rollup-linux-arm64-musl': 4.53.5 + '@rollup/rollup-linux-loong64-gnu': 4.53.5 + '@rollup/rollup-linux-ppc64-gnu': 4.53.5 + '@rollup/rollup-linux-riscv64-gnu': 4.53.5 + '@rollup/rollup-linux-riscv64-musl': 4.53.5 + '@rollup/rollup-linux-s390x-gnu': 4.53.5 + '@rollup/rollup-linux-x64-gnu': 4.53.5 + '@rollup/rollup-linux-x64-musl': 4.53.5 + '@rollup/rollup-openharmony-arm64': 4.53.5 + '@rollup/rollup-win32-arm64-msvc': 4.53.5 + '@rollup/rollup-win32-ia32-msvc': 4.53.5 + '@rollup/rollup-win32-x64-gnu': 4.53.5 + '@rollup/rollup-win32-x64-msvc': 4.53.5 fsevents: 2.3.3 rrweb-cssom@0.7.1: {} @@ -6101,12 +6151,6 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.0 - string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 @@ -6120,10 +6164,6 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.0: - dependencies: - ansi-regex: 6.1.0 - strip-bom@3.0.0: {} strip-final-newline@3.0.0: {} @@ -6216,6 +6256,11 @@ snapshots: fdir: 6.4.4(picomatch@4.0.2) picomatch: 4.0.2 + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + tinypool@1.1.1: {} tinyrainbow@2.0.0: {} @@ -6351,9 +6396,10 @@ snapshots: debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 5.4.19(sass@1.82.0)(terser@5.37.0) + vite: 7.3.0(sass@1.82.0)(terser@5.37.0) transitivePeerDependencies: - '@types/node' + - jiti - less - lightningcss - sass @@ -6362,11 +6408,13 @@ snapshots: - sugarss - supports-color - terser + - tsx + - yaml - vite-plugin-dts@4.3.0(rollup@4.28.1)(typescript@5.7.2)(vite@5.4.19(sass@1.82.0)(terser@5.37.0)): + vite-plugin-dts@4.3.0(rollup@4.53.5)(typescript@5.7.2)(vite@7.3.0(sass@1.82.0)(terser@5.37.0)): dependencies: '@microsoft/api-extractor': 7.48.0 - '@rollup/pluginutils': 5.1.3(rollup@4.28.1) + '@rollup/pluginutils': 5.1.3(rollup@4.53.5) '@volar/typescript': 2.4.10 '@vue/language-core': 2.1.6(typescript@5.7.2) compare-versions: 6.1.1 @@ -6376,38 +6424,41 @@ snapshots: magic-string: 0.30.15 typescript: 5.7.2 optionalDependencies: - vite: 5.4.19(sass@1.82.0)(terser@5.37.0) + vite: 7.3.0(sass@1.82.0)(terser@5.37.0) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite@5.4.19(sass@1.82.0)(terser@5.37.0): + vite@7.3.0(sass@1.82.0)(terser@5.37.0): dependencies: - esbuild: 0.25.4 - postcss: 8.4.49 - rollup: 4.28.1 + esbuild: 0.27.1 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.53.5 + tinyglobby: 0.2.15 optionalDependencies: fsevents: 2.3.3 sass: 1.82.0 terser: 5.37.0 - vitepress-plugin-group-icons@1.5.5(markdown-it@14.1.0)(vite@5.4.19(sass@1.82.0)(terser@5.37.0)): + vitepress-plugin-group-icons@1.5.5(markdown-it@14.1.0)(vite@7.3.0(sass@1.82.0)(terser@5.37.0)): dependencies: '@iconify-json/logos': 1.2.4 '@iconify-json/vscode-icons': 1.2.21 '@iconify/utils': 2.3.0 markdown-it: 14.1.0 - vite: 5.4.19(sass@1.82.0)(terser@5.37.0) + vite: 7.3.0(sass@1.82.0)(terser@5.37.0) transitivePeerDependencies: - supports-color - vitepress-plugin-tabs@0.7.1(vitepress@1.6.3(@algolia/client-search@5.25.0)(postcss@8.4.49)(sass@1.82.0)(search-insights@2.17.3)(terser@5.37.0)(typescript@5.7.2))(vue@3.5.13(typescript@5.7.2)): + vitepress-plugin-tabs@0.7.1(vitepress@1.6.3(@algolia/client-search@5.25.0)(postcss@8.5.6)(sass@1.82.0)(search-insights@2.17.3)(terser@5.37.0)(typescript@5.7.2))(vue@3.5.13(typescript@5.7.2)): dependencies: - vitepress: 1.6.3(@algolia/client-search@5.25.0)(postcss@8.4.49)(sass@1.82.0)(search-insights@2.17.3)(terser@5.37.0)(typescript@5.7.2) + vitepress: 1.6.3(@algolia/client-search@5.25.0)(postcss@8.5.6)(sass@1.82.0)(search-insights@2.17.3)(terser@5.37.0)(typescript@5.7.2) vue: 3.5.13(typescript@5.7.2) - vitepress@1.6.3(@algolia/client-search@5.25.0)(postcss@8.4.49)(sass@1.82.0)(search-insights@2.17.3)(terser@5.37.0)(typescript@5.7.2): + vitepress@1.6.3(@algolia/client-search@5.25.0)(postcss@8.5.6)(sass@1.82.0)(search-insights@2.17.3)(terser@5.37.0)(typescript@5.7.2): dependencies: '@docsearch/css': 3.8.2 '@docsearch/js': 3.8.2(@algolia/client-search@5.25.0)(search-insights@2.17.3) @@ -6416,7 +6467,7 @@ snapshots: '@shikijs/transformers': 2.5.0 '@shikijs/types': 2.5.0 '@types/markdown-it': 14.1.2 - '@vitejs/plugin-vue': 5.2.1(vite@5.4.19(sass@1.82.0)(terser@5.37.0))(vue@3.5.13(typescript@5.7.2)) + '@vitejs/plugin-vue': 5.2.1(vite@7.3.0(sass@1.82.0)(terser@5.37.0))(vue@3.5.13(typescript@5.7.2)) '@vue/devtools-api': 7.7.6 '@vue/shared': 3.5.13 '@vueuse/core': 12.8.2(typescript@5.7.2) @@ -6425,10 +6476,10 @@ snapshots: mark.js: 8.11.1 minisearch: 7.1.2 shiki: 2.5.0 - vite: 5.4.19(sass@1.82.0)(terser@5.37.0) + vite: 7.3.0(sass@1.82.0)(terser@5.37.0) vue: 3.5.13(typescript@5.7.2) optionalDependencies: - postcss: 8.4.49 + postcss: 8.5.6 transitivePeerDependencies: - '@algolia/client-search' - '@types/node' @@ -6439,6 +6490,7 @@ snapshots: - drauu - fuse.js - idb-keyval + - jiti - jwt-decode - less - lightningcss @@ -6453,14 +6505,16 @@ snapshots: - stylus - sugarss - terser + - tsx - typescript - universal-cookie + - yaml vitest@3.2.4(@vitest/ui@3.2.4)(jsdom@25.0.1)(sass@1.82.0)(terser@5.37.0): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@5.4.19(sass@1.82.0)(terser@5.37.0)) + '@vitest/mocker': 3.2.4(vite@7.3.0(sass@1.82.0)(terser@5.37.0)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -6478,13 +6532,14 @@ snapshots: tinyglobby: 0.2.14 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 5.4.19(sass@1.82.0)(terser@5.37.0) + vite: 7.3.0(sass@1.82.0)(terser@5.37.0) vite-node: 3.2.4(sass@1.82.0)(terser@5.37.0) why-is-node-running: 2.3.0 optionalDependencies: '@vitest/ui': 3.2.4(vitest@3.2.4) jsdom: 25.0.1 transitivePeerDependencies: + - jiti - less - lightningcss - msw @@ -6494,6 +6549,8 @@ snapshots: - sugarss - supports-color - terser + - tsx + - yaml vscode-uri@3.0.8: {} @@ -6563,12 +6620,6 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.1 - string-width: 5.1.2 - strip-ansi: 7.1.0 - ws@8.18.0: {} xml-name-validator@4.0.0: {} diff --git a/src/builders/base/BaseInput.ts b/src/builders/base/BaseInput.ts index 97fe15d..e1a47ad 100644 --- a/src/builders/base/BaseInput.ts +++ b/src/builders/base/BaseInput.ts @@ -7,8 +7,8 @@ import { BaseFieldBuilder } from '@/builders/base' */ export default class BaseInputBuilder extends BaseFieldBuilder { - constructor(name: string, model: string) { - super('input', name, model) + constructor(name: string) { + super('input', name) } getRequiredKeys(): (keyof T)[] { diff --git a/src/builders/base/BaseOption.ts b/src/builders/base/BaseOption.ts index 847cb72..5c5d494 100644 --- a/src/builders/base/BaseOption.ts +++ b/src/builders/base/BaseOption.ts @@ -15,8 +15,8 @@ export default class BaseOptionFieldBuilder implements IBaseOptionFieldBuilder { - constructor(type:string, name: string, model: string) { - super(type, name, model) + constructor(type:string, name: string) { + super(type, name) this.data.options = [] } diff --git a/src/builders/base/index.ts b/src/builders/base/index.ts index 215616a..dad0903 100644 --- a/src/builders/base/index.ts +++ b/src/builders/base/index.ts @@ -11,12 +11,11 @@ export interface IBaseBuilder { * The base class for every builder. */ export abstract class BaseBuilder implements IBaseBuilder { - protected data: Partial = {} - protected constructor (type: string, name: string, model: string) { + protected constructor (type: string, name: string) { this.data = { - type, name, model + type, name } as Partial } @@ -70,12 +69,19 @@ export interface IBaseFieldBuilder extends IBaseBuilder visible (value?: FieldBase['visible']): this readonly (value?: FieldBase['readonly']): this disabled (value?: FieldBase['disabled']): this + validator (value?: NonNullable): this + onValidated (value: NonNullable): this + validateOn (value: NonNullable): this + model (key: string): this + default (value: any): this + getInitialValue (): any } /** * Base class for most FieldBuilder classes. */ export abstract class BaseFieldBuilder extends BaseBuilder implements IBaseFieldBuilder { + protected __default__: any = null /** * Set the id property of the field. @@ -191,4 +197,28 @@ export abstract class BaseFieldBuilder extends BaseBuil return this } + /** + * Explicitly set the model key of the field. + * @param key + */ + model (key: string): this { + this.data.model = key + return this + } + + /** + * Get the initial value of the field. + */ + getInitialValue (): any { + return this.__default__ + } + + /** + * Set the initial value of the field. + */ + default (value: any): this { + this.__default__ = value + return this + } + } diff --git a/src/builders/fields/CheckboxFieldBuilder.ts b/src/builders/fields/CheckboxFieldBuilder.ts index d9d80b5..df2822a 100644 --- a/src/builders/fields/CheckboxFieldBuilder.ts +++ b/src/builders/fields/CheckboxFieldBuilder.ts @@ -2,9 +2,10 @@ import type { CheckboxField } from '@/resources/types/field/fields' import BaseInputBuilder from '@/builders/base/BaseInput' export default class CheckboxFieldBuilder extends BaseInputBuilder { + protected __default__: boolean = false - constructor(name: string, model: string) { - super(name, model) + constructor(name: string) { + super(name) this.data.inputType = 'checkbox' } diff --git a/src/builders/fields/ChecklistFieldBuilder.ts b/src/builders/fields/ChecklistFieldBuilder.ts index 1b7cf93..ea06340 100644 --- a/src/builders/fields/ChecklistFieldBuilder.ts +++ b/src/builders/fields/ChecklistFieldBuilder.ts @@ -2,9 +2,10 @@ import BaseOptionFieldBuilder from '@/builders/base/BaseOption' import { ChecklistField } from '@/resources/types/field/fields' export default class ChecklistFieldBuilder extends BaseOptionFieldBuilder { + protected __default__: string[] = [] - constructor(name: string, model: string) { - super('checklist', name, model) + constructor(name: string) { + super('checklist', name) } diff --git a/src/builders/fields/TextFieldBuilder.ts b/src/builders/fields/TextFieldBuilder.ts index 0baf258..b532421 100644 --- a/src/builders/fields/TextFieldBuilder.ts +++ b/src/builders/fields/TextFieldBuilder.ts @@ -11,9 +11,10 @@ export interface ITextFieldBuilder extends IBaseFieldBuilder { * Field builder for the text field. */ export default class TextFieldBuilder extends BaseInputBuilder implements ITextFieldBuilder { + protected __default__: string = '' - constructor(name: string, model: string) { - super(name, model) + constructor(name: string) { + super(name) this.data.inputType = 'text' } diff --git a/src/builders/index.ts b/src/builders/index.ts index 5c26a21..6e20921 100644 --- a/src/builders/index.ts +++ b/src/builders/index.ts @@ -6,39 +6,6 @@ import TextFieldBuilder from '@/builders/fields/TextFieldBuilder' import CheckboxFieldBuilder from '@/builders/fields/CheckboxFieldBuilder' import ChecklistFieldBuilder from '@/builders/fields/ChecklistFieldBuilder' -import type { FormModel } from '@/resources/types/fieldAttributes' - -type SchemaItem = BaseFieldBuilder | GroupBuilder - -function schema(model: FormModel): SchemaBuilder -function schema(model: FormModel, ...items: BaseFieldBuilder[]): SchemaBuilder -function schema(model: FormModel, ...items: GroupBuilder[]): SchemaBuilder -function schema(model: FormModel, ...items: SchemaItem[]): SchemaBuilder -function schema( - model: FormModel, - ...items: SchemaItem[] -): SchemaBuilder { - if (!items || items.length === 0) { - return new SchemaBuilder(model, undefined, undefined) - } - - const fields: BaseFieldBuilder[] = [] - const groups: GroupBuilder[] = [] - - items.forEach(item => { - if (item instanceof GroupBuilder) { - groups.push(item) - } else { - fields.push(item as BaseFieldBuilder) - } - }) - - return new SchemaBuilder( - model, - fields.length > 0 ? fields : undefined, - groups.length > 0 ? groups : undefined - ) -} export const f = { group: (legend?: string, ...fields: BaseFieldBuilder[])=> { @@ -48,85 +15,70 @@ export const f = { * Returns a form schema builder instance. * Takes a model object as the first argument. Takes field builders and group builders as the rest of the arguments. * - * The order of field builders and group builders will be reflected inside the rendered form. - * * @example - * // With field builders - * f.schema( - * { - * username: '' - * role: '' - * }, - * f.text('Username', 'username').placeholder('Enter your username'), - * f.select('Role', 'role').addOption('Admin', 'admin') - * ).build() - * - * // With groups - * f.schema( - * { - * username: '' - * role: '' - * }, - * f.group( - * 'User details', - * f.text('Username', 'username').placeholder('Enter your username'), - * f.select('Role', 'role').addOption('Admin', 'admin') - * ) - * ).build() - * - * // With groups and field builders - * f.schema( - * { - * username: '' - * role: '', - * terms: false - * }, - * f.group( - * 'User details', - * f.text('Username', 'username').placeholder('Enter your username'), - * f.select('Role', 'role').addOption('Admin', 'admin') - * ), - * f.checkbox('Terms and conditions', 'terms').label('I agree to the terms and conditions') - * ).build() + * // Input + * f.schema({ + * name: f.text('Name').placeholder('Enter your name') + * }) + * // Output + * { + * model: { name: '' }, + * schema: { + * fields: [ + * { + * type: 'input', + * inputType: 'text', + * name: 'Name', + * model: 'name', + * placeholder: 'Enter your name' + * } + * ] + * } + * } */ - schema, + schema: >>( + schema: S, + defaults?: Partial> + ): SchemaBuilder => new SchemaBuilder(schema, defaults), /** * Create a text field builder instance. * * @example - * // As builder - * f.text('fieldName', 'modelKey').placeholder('Field placeholder') - * // As JSON - * f.text('fieldName', 'modelKey').placeholder('Field placeholder').build() - * @param name - * @param model + * // Inside a schema + * f.schema({ + * fieldModel: f.text('fieldName').placeholder('Field placeholder') + * }) + * @param name - Name of the field. * @returns A text field builder instance. */ - text: (name: string, model: string): TextFieldBuilder => new TextFieldBuilder(name, model), + text: (name: string): TextFieldBuilder => { + return new TextFieldBuilder(name) + }, /** * Create a checkbox field builder instance. * @example - * // As builder - * f.checkbox('fieldName', 'modelKey').label('Checkbox label') + * // Inside a schema + * f.schema({ + * fieldModel: f.checkbox('fieldName').label('Checkbox label') + * }) * @param name - * @param model * @returns A checkbox field builder instance. */ - checkbox: (name: string, model: string): CheckboxFieldBuilder => new CheckboxFieldBuilder(name, model), + checkbox: (name: string): CheckboxFieldBuilder => new CheckboxFieldBuilder(name), /** * Create a checklist field builder instance. * @example - * // As builder - * f.checklist('fieldName', 'modelKey').options([ - * { name: 'All', value: 'all' }, - * { name: 'Supervisor', value: 'supervisor' } - * ]) - * // Or... - * f.checklist('fieldName', 'modelKey').option('All', 'all').option('Supervisor', 'supervisor') + * f.schema({ + * fieldModel: f.checklist('fieldName').options([ + * { name: 'All', value: 'all' }, + * { name: 'Supervisor', value: 'supervisor' } + * ]), + * // Or... + * fieldModel2: f.checklist('fieldName').option('All', 'all').option('Supervisor', 'supervisor') + * }) * @param name - * @param model * @returns A checkbox field builder instance. */ - checklist: (name: string, model: string): ChecklistFieldBuilder => new ChecklistFieldBuilder(name, model) + checklist: (name: string): ChecklistFieldBuilder => new ChecklistFieldBuilder(name) } diff --git a/src/builders/schema.ts b/src/builders/schema.ts index dd7f01b..8fe05e1 100644 --- a/src/builders/schema.ts +++ b/src/builders/schema.ts @@ -1,23 +1,31 @@ import { BaseFieldBuilder } from '@/builders/base' import { FormGeneratorSchema } from '@/resources/types/generic' -import GroupBuilder from '@/builders/group' import { type Ref, ref } from 'vue' /** * Form schema builder - * @param model - Initial model object. - * @param fields - Array of field builders. - * @param groups - Array of group builders. + * @param schema - An object containing field builders. The key of the object is the model key of the field, the value is the field builder instance. */ -export class SchemaBuilder { +export class SchemaBuilder >> { protected model: Record = {} protected fields: BaseFieldBuilder[] = [] - protected groups: GroupBuilder[] = [] - constructor(model: Record, fields?: BaseFieldBuilder[], groups?: GroupBuilder[]) { - this.model = model - if (fields) this.fields = fields - if (groups) this.groups = groups + constructor(schema: S, defaults?: Partial>) { + for (const [ modelKey, field ] of Object.entries(schema) ) { + try { + this.model[modelKey] = field.getInitialValue() + const addField = field.model(modelKey) + if (defaults && defaults[modelKey]) { + addField.default(defaults[modelKey]) + } + this.field(addField) + } catch (e) { + if (typeof field.getInitialValue !== 'function') { + throw new Error(`Error initializing field ${modelKey}: Field might not be a field builder instance.`) + } + throw new Error(`Error initializing field ${modelKey}: ${e}`) + } + } } /** @@ -28,33 +36,15 @@ export class SchemaBuilder { return this.fields } - /** - * Get an array of all groups - * @returns A array of groups. - */ - getGroups (): GroupBuilder[] { - return this.groups - } - /** * Add a field to the schema. * @param field - Field builder instance of the field that needs to be added. */ - field (field: BaseFieldBuilder): SchemaBuilder { + field (field: BaseFieldBuilder): SchemaBuilder { this.fields.push(field) return this } - /** - * Add a group to the schema. - * @param legend - Optional legend for the group. - * @param fields - Array of field builders that need to be added to the group. - */ - group (legend?: string, ...fields: BaseFieldBuilder[]): SchemaBuilder { - this.groups.push(new GroupBuilder(fields, legend)) - return this - } - /** * Build the schema and return as a Ref. * @returns Built JSON schema wrapped in a Ref. @@ -71,8 +61,7 @@ export class SchemaBuilder { return { model: this.model, schema: { - fields: this.fields?.map((field: BaseFieldBuilder) => field.build()) ?? undefined, - groups: this.groups?.map((group: GroupBuilder) => group.build()) ?? undefined + fields: this.fields?.map((field: BaseFieldBuilder) => field.build()) ?? undefined } } } diff --git a/src/index.ts b/src/index.ts index 5612b20..7e8a4b7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -71,6 +71,7 @@ export { validators } +export { BaseFieldBuilder, type IBaseFieldBuilder } from '@/builders/base' export { f } from '@/builders/index' export type { diff --git a/tests/builders/BaseBuilder.spec.ts b/tests/builders/BaseBuilder.spec.ts index 597167b..d07184f 100644 --- a/tests/builders/BaseBuilder.spec.ts +++ b/tests/builders/BaseBuilder.spec.ts @@ -4,13 +4,13 @@ import { FieldBase } from '@/resources/types/field/base' type TestField = FieldBase & { [key: string]: any } class TestBaseBuilder extends BaseBuilder { - constructor(name: string, model: string) { - super('test', name, model) + constructor(name: string) { + super('test', name) } } function getBuilder () { - return new TestBaseBuilder('testName', 'testModel') + return new TestBaseBuilder('testName').model('testModel') } describe('BaseBuilder', () => { diff --git a/tests/builders/BaseFieldBuilder.spec.ts b/tests/builders/BaseFieldBuilder.spec.ts index 7cf12fc..b7889fa 100644 --- a/tests/builders/BaseFieldBuilder.spec.ts +++ b/tests/builders/BaseFieldBuilder.spec.ts @@ -9,8 +9,8 @@ type TestField = FieldBase & { } class TestBaseFieldBuilder extends BaseFieldBuilder { - constructor(name: string, model: string) { - super('test', name, model) + constructor(name: string) { + super('test', name) } testKey (value: string): this { @@ -26,11 +26,11 @@ class TestBaseFieldBuilder extends BaseFieldBuilder { } function testField (name: string, model: string): TestBaseFieldBuilder { - return new TestBaseFieldBuilder(name, model) + return new TestBaseFieldBuilder(name).model(model) } function getBuilder (): TestBaseFieldBuilder { - return new TestBaseFieldBuilder('testField', 'testModel').testKey('test') + return new TestBaseFieldBuilder('testField').model('testModel').testKey('test') } describe('BaseFieldBuilder', () => { diff --git a/tests/builders/SchemaBuilder.spec.ts b/tests/builders/SchemaBuilder.spec.ts index aee5e01..cd256da 100644 --- a/tests/builders/SchemaBuilder.spec.ts +++ b/tests/builders/SchemaBuilder.spec.ts @@ -3,62 +3,26 @@ import { SchemaBuilder } from '@/builders/schema' import { isRef } from 'vue' import TextFieldBuilder from '@/builders/fields/TextFieldBuilder' -import GroupBuilder from '@/builders/group' -const getBuilderNoGroups = () => new SchemaBuilder({}, [ - new TextFieldBuilder('testField', 'testModel') -], undefined) +const getBuilderNoGroups = () => new SchemaBuilder({ + testField: new TextFieldBuilder('testField') +}) describe('SchemaBuilder', () => { describe('constructor', () => { it('Should instantiate with field builders', () => { - const builder = new SchemaBuilder({}, - [ - new TextFieldBuilder('testField', 'testModel'), - new TextFieldBuilder('testField2', 'testModel2') - ] + const builder = new SchemaBuilder({ + testField: new TextFieldBuilder('testField'), + testField2: new TextFieldBuilder('testField2') + } ) expect(builder.getFields()).toHaveLength(2) const onlyBuilders = builder.getFields().filter(field => field instanceof TextFieldBuilder) expect(onlyBuilders).toHaveLength(2) }) - it('Should instantiate with groups', () => { - const builder = new SchemaBuilder({}, [], [ - new GroupBuilder([ - new TextFieldBuilder('testField', 'testModel'), - new TextFieldBuilder('testField2', 'testModel2') - ], 'testGroup') - ]) - expect(builder.getFields()).toHaveLength(0) - expect(builder.getGroups()).toHaveLength(1) - }) - - it('Should instantiate with both field builders and groups', () => { - const builder = new SchemaBuilder({}, - [ - new TextFieldBuilder('testField', 'testModel'), - new TextFieldBuilder('testField2', 'testModel2') - ], - [ - new GroupBuilder([ - new TextFieldBuilder('testField', 'testModel'), - new TextFieldBuilder('testField2', 'testModel2') - ], 'testGroup') - ] - ) - expect(builder.getFields()).toHaveLength(2) - expect(builder.getGroups()).toHaveLength(1) - }) - - it('Should instantiate without field builders or groups', () => { - expect(() => { - new SchemaBuilder({}, undefined, undefined) - }).not.toThrowError() - }) - }) describe('field()', () => { @@ -66,26 +30,13 @@ describe('SchemaBuilder', () => { it('Should add a field builder to the schema', () => { const builder = getBuilderNoGroups() expect(builder.getFields().length).toBe(1) - builder.field(new TextFieldBuilder('name2', 'model2')) + builder.field(new TextFieldBuilder('name2').model('model2')) expect(builder.getFields().length).toBe(2) expect(builder.getFields()[1].__data__().name).toBe('name2') }) }) - describe('group()', () => { - - it('Should add a group builder to the schema', () => { - const builder = new SchemaBuilder({}, [], [ - new GroupBuilder([], 'testGroup') - ]) - expect(builder.getGroups()).toHaveLength(1) - builder.group('testGroup2', new TextFieldBuilder('name2', 'model2'), new TextFieldBuilder('name1', 'model1')) - expect(builder.getGroups()).toHaveLength(2) - }) - - }) - describe('toRef()', () => { it('Should return a ref', () => { @@ -108,18 +59,10 @@ describe('SchemaBuilder', () => { it('Should return a complete schema as JSON object', () => { const builder = new SchemaBuilder({ - username: '', - name: '', - surname: '' - }, [ - new TextFieldBuilder('username', 'username') - ], - [ - new GroupBuilder([ - new TextFieldBuilder('name', 'name'), - new TextFieldBuilder('surname', 'surname') - ], 'Personal details') - ]) + username: new TextFieldBuilder('username'), + name: new TextFieldBuilder('name'), + surname: new TextFieldBuilder('surname') + }) const formSchema = builder.build() expect(formSchema.model).toBeDefined() expect(formSchema.model.username).toBeDefined() @@ -129,37 +72,9 @@ describe('SchemaBuilder', () => { const schema = formSchema.schema expect(schema).toBeDefined() expect(schema.fields).toBeDefined() - expect(schema.groups).toBeDefined() expect(schema.fields[0].name).toBe('username') expect(schema.fields[0].model).toBe('username') - - const group = schema.groups[0] - expect(group).toBeDefined() - expect(group.fields[0].name).toBe('name') - expect(group.fields[0].model).toBe('name') - expect(group.fields[0].type).toBe('input') - expect(group.fields[0].inputType).toBe('text') - }) - - it('Should fail when provided items are pre-built', () => { - const builder = new SchemaBuilder({ - username: '', - name: '', - surname: '' - }, [ - // @ts-expect-error: Pre-built field builder - new TextFieldBuilder('username', 'username').build() - ], - [ - new GroupBuilder([ - new TextFieldBuilder('name', 'name'), - new TextFieldBuilder('surname', 'surname') - ], 'Personal details').build() - ]) - expect(() => { - builder.build() - }).toThrowError() }) }) diff --git a/tests/builders/TextFieldBuilder.spec.ts b/tests/builders/TextFieldBuilder.spec.ts index a4d755c..f50f9eb 100644 --- a/tests/builders/TextFieldBuilder.spec.ts +++ b/tests/builders/TextFieldBuilder.spec.ts @@ -1,20 +1,20 @@ import { it, expect, describe } from 'vitest' import TextFieldBuilder from '@/builders/fields/TextFieldBuilder' -const getBuilder = () => new TextFieldBuilder('fieldName', 'fieldModel') +const getBuilder = () => new TextFieldBuilder('fieldName').model('fieldModel') describe('TextFieldBuilder', () => { describe('constructor', () => { it('Should instantiate with name and model', () => { - const builder = new TextFieldBuilder('testField', 'testModel') + const builder = new TextFieldBuilder('testField').model('testModel') expect(builder.__data__().name).toBe('testField') expect(builder.__data__().model).toBe('testModel') }) it('Should instantiate with the right types', () => { - const builder = new TextFieldBuilder('testField', 'testModel') + const builder = new TextFieldBuilder('testField').model('testModel') expect(builder.__data__().type).toBe('input') expect(builder.__data__().inputType).toBe('text') }) From a889ce507dfbf587a0e24ac4b9710f4c3f836ba4 Mon Sep 17 00:00:00 2001 From: kevinkosterr Date: Tue, 16 Dec 2025 17:52:10 +0100 Subject: [PATCH 13/15] WIP: fix --- src/builders/base/index.ts | 67 ++++++++++--------- ...Builder.spec.ts => AbstractBaseBuilder.ts} | 6 +- 2 files changed, 37 insertions(+), 36 deletions(-) rename tests/builders/{BaseBuilder.spec.ts => AbstractBaseBuilder.ts} (88%) diff --git a/src/builders/base/index.ts b/src/builders/base/index.ts index dad0903..3677968 100644 --- a/src/builders/base/index.ts +++ b/src/builders/base/index.ts @@ -1,16 +1,21 @@ import type { Field } from '@/resources/types/field/fields' import type { FieldBase } from '@/resources/types/field/base' -export interface IBaseBuilder { +export interface IAbstractBaseBuilder { __data__ (): Partial extend (properties: Record): this build (): T + model (key: string): this + default (value: any): this + getInitialValue (): any } /** - * The base class for every builder. + * The base class for every builder. Contains the absolute basics for every field builder. */ -export abstract class BaseBuilder implements IBaseBuilder { +export abstract class AbstractBaseBuilder implements IAbstractBaseBuilder { + protected __default__: any = null + protected data: Partial = {} protected constructor (type: string, name: string) { @@ -31,6 +36,30 @@ export abstract class BaseBuilder implements IBaseBuild return [ 'type', 'model' ] as (keyof T)[] } + /** + * Explicitly set the model key of the field. + * @param key + */ + model (key: string): this { + this.data.model = key + return this + } + + /** + * Get the initial value of the field. + */ + getInitialValue (): any { + return this.__default__ + } + + /** + * Set the initial value of the field. + */ + default (value: any): this { + this.__default__ = value + return this + } + /** * Extend the current field with additional properties. * @param properties @@ -59,7 +88,7 @@ export abstract class BaseBuilder implements IBaseBuild } -export interface IBaseFieldBuilder extends IBaseBuilder { +export interface IBaseFieldBuilder extends IAbstractBaseBuilder { id (value: NonNullable): this label(value: NonNullable): this labelIcon (value: NonNullable): this @@ -72,16 +101,12 @@ export interface IBaseFieldBuilder extends IBaseBuilder validator (value?: NonNullable): this onValidated (value: NonNullable): this validateOn (value: NonNullable): this - model (key: string): this - default (value: any): this - getInitialValue (): any } /** * Base class for most FieldBuilder classes. */ -export abstract class BaseFieldBuilder extends BaseBuilder implements IBaseFieldBuilder { - protected __default__: any = null +export abstract class BaseFieldBuilder extends AbstractBaseBuilder implements IBaseFieldBuilder { /** * Set the id property of the field. @@ -197,28 +222,4 @@ export abstract class BaseFieldBuilder extends BaseBuil return this } - /** - * Explicitly set the model key of the field. - * @param key - */ - model (key: string): this { - this.data.model = key - return this - } - - /** - * Get the initial value of the field. - */ - getInitialValue (): any { - return this.__default__ - } - - /** - * Set the initial value of the field. - */ - default (value: any): this { - this.__default__ = value - return this - } - } diff --git a/tests/builders/BaseBuilder.spec.ts b/tests/builders/AbstractBaseBuilder.ts similarity index 88% rename from tests/builders/BaseBuilder.spec.ts rename to tests/builders/AbstractBaseBuilder.ts index d07184f..f0a7dc3 100644 --- a/tests/builders/BaseBuilder.spec.ts +++ b/tests/builders/AbstractBaseBuilder.ts @@ -1,9 +1,9 @@ import { describe, it, expect } from 'vitest' -import { BaseBuilder } from '@/builders/base' +import { AbstractBaseBuilder } from '@/builders/base' import { FieldBase } from '@/resources/types/field/base' type TestField = FieldBase & { [key: string]: any } -class TestBaseBuilder extends BaseBuilder { +class TestBaseBuilder extends AbstractBaseBuilder { constructor(name: string) { super('test', name) } @@ -13,7 +13,7 @@ function getBuilder () { return new TestBaseBuilder('testName').model('testModel') } -describe('BaseBuilder', () => { +describe('AbstractBaseBuilder', () => { describe('extend()', () => { From 0747f7c2b0f4c03eab0bc5acdc97b625d4b5d8aa Mon Sep 17 00:00:00 2001 From: kevinkosterr Date: Wed, 17 Dec 2025 22:01:34 +0100 Subject: [PATCH 14/15] WIP: add typescript model value types, NoValueError exception --- src/builders/base/BaseInput.ts | 2 +- src/builders/base/BaseOption.ts | 6 +- src/builders/base/index.ts | 39 ++++---- src/builders/fields/CheckboxFieldBuilder.ts | 4 +- src/builders/fields/ChecklistFieldBuilder.ts | 4 +- src/builders/fields/TextFieldBuilder.ts | 6 +- src/builders/index.ts | 21 ++-- src/builders/schema.ts | 99 +++++++++++++++---- src/exceptions.ts | 11 +++ src/index.ts | 1 + src/resources/types/generic.ts | 12 ++- ...Builder.ts => AbstractBaseBuilder.spec.ts} | 0 tests/builders/BaseFieldBuilder.spec.ts | 4 +- tests/builders/SchemaBuilder.spec.ts | 15 +-- 14 files changed, 149 insertions(+), 75 deletions(-) create mode 100644 src/exceptions.ts rename tests/builders/{AbstractBaseBuilder.ts => AbstractBaseBuilder.spec.ts} (100%) diff --git a/src/builders/base/BaseInput.ts b/src/builders/base/BaseInput.ts index e1a47ad..cb08d27 100644 --- a/src/builders/base/BaseInput.ts +++ b/src/builders/base/BaseInput.ts @@ -5,7 +5,7 @@ import { BaseFieldBuilder } from '@/builders/base' /** * Base class for all input field builders e.g., TextFieldBuilder or CheckboxFieldBuilder. */ -export default class BaseInputBuilder extends BaseFieldBuilder { +export default class BaseInputBuilder extends BaseFieldBuilder { constructor(name: string) { super('input', name) diff --git a/src/builders/base/BaseOption.ts b/src/builders/base/BaseOption.ts index 5c5d494..971e49f 100644 --- a/src/builders/base/BaseOption.ts +++ b/src/builders/base/BaseOption.ts @@ -3,7 +3,7 @@ import type { FieldOption } from '@/resources/types/fieldAttributes' import { Field } from '@/resources/types/field/fields' import type { OptionField } from '@/resources/types/field/base' -interface IBaseOptionFieldBuilder extends IBaseFieldBuilder { +interface IBaseOptionFieldBuilder extends IBaseFieldBuilder { option (name: FieldOption['name'], value: FieldOption['value']): this options(options: FieldOption[]): this } @@ -11,8 +11,8 @@ interface IBaseOptionFieldBuilder extends IBaseFieldBuilder /** * Base class for all option field builders e.g., SelectFieldBuilder or RadioFieldBuilder. */ -export default class BaseOptionFieldBuilder - extends BaseFieldBuilder implements IBaseOptionFieldBuilder +export default class BaseOptionFieldBuilder + extends BaseFieldBuilder implements IBaseOptionFieldBuilder { constructor(type:string, name: string) { diff --git a/src/builders/base/index.ts b/src/builders/base/index.ts index 3677968..bf324e2 100644 --- a/src/builders/base/index.ts +++ b/src/builders/base/index.ts @@ -1,30 +1,31 @@ import type { Field } from '@/resources/types/field/fields' import type { FieldBase } from '@/resources/types/field/base' -export interface IAbstractBaseBuilder { - __data__ (): Partial +export interface IAbstractBaseBuilder { + __default__: V + __data__ (): Partial extend (properties: Record): this - build (): T + build (): F model (key: string): this default (value: any): this - getInitialValue (): any + getDefaultValue (): this['__default__'] } /** * The base class for every builder. Contains the absolute basics for every field builder. */ -export abstract class AbstractBaseBuilder implements IAbstractBaseBuilder { - protected __default__: any = null +export abstract class AbstractBaseBuilder implements IAbstractBaseBuilder { + __default__!: V - protected data: Partial = {} + protected data: Partial = {} protected constructor (type: string, name: string) { this.data = { type, name - } as Partial + } as Partial } - __data__ (): Partial { return this.data } + __data__ (): Partial { return this.data } /** * Gets the keys required for this field. @@ -32,8 +33,8 @@ export abstract class AbstractBaseBuilder implements IA * * @protected */ - protected getRequiredKeys (): (keyof T)[] { - return [ 'type', 'model' ] as (keyof T)[] + protected getRequiredKeys (): (keyof F)[] { + return [ 'type', 'model' ] as (keyof F)[] } /** @@ -46,9 +47,9 @@ export abstract class AbstractBaseBuilder implements IA } /** - * Get the initial value of the field. + * Get the default value of the field. */ - getInitialValue (): any { + getDefaultValue (): this['__default__'] { return this.__default__ } @@ -73,9 +74,9 @@ export abstract class AbstractBaseBuilder implements IA * Build and validate the field object. * Ensures all required properties are present. */ - build (): T { - const requiredKeys: (keyof T)[] = this.getRequiredKeys() - const missingKeys: (keyof T)[] = requiredKeys.filter((key: keyof T) => { + build (): F { + const requiredKeys: (keyof F)[] = this.getRequiredKeys() + const missingKeys: (keyof F)[] = requiredKeys.filter((key: keyof F) => { return !(key in this.data) && (this.data[key] === undefined || this.data[key] === null || this.data[key] === '') }) @@ -83,12 +84,12 @@ export abstract class AbstractBaseBuilder implements IA throw new Error(`Failed to build field. Missing required keys: ${missingKeys.join(', ')}`) } - return this.data as T + return this.data as F } } -export interface IBaseFieldBuilder extends IAbstractBaseBuilder { +export interface IBaseFieldBuilder extends IAbstractBaseBuilder { id (value: NonNullable): this label(value: NonNullable): this labelIcon (value: NonNullable): this @@ -106,7 +107,7 @@ export interface IBaseFieldBuilder extends IAbstractBas /** * Base class for most FieldBuilder classes. */ -export abstract class BaseFieldBuilder extends AbstractBaseBuilder implements IBaseFieldBuilder { +export abstract class BaseFieldBuilder extends AbstractBaseBuilder implements IBaseFieldBuilder { /** * Set the id property of the field. diff --git a/src/builders/fields/CheckboxFieldBuilder.ts b/src/builders/fields/CheckboxFieldBuilder.ts index df2822a..b34e0e2 100644 --- a/src/builders/fields/CheckboxFieldBuilder.ts +++ b/src/builders/fields/CheckboxFieldBuilder.ts @@ -1,8 +1,8 @@ import type { CheckboxField } from '@/resources/types/field/fields' import BaseInputBuilder from '@/builders/base/BaseInput' -export default class CheckboxFieldBuilder extends BaseInputBuilder { - protected __default__: boolean = false +export default class CheckboxFieldBuilder extends BaseInputBuilder { + __default__: boolean = false constructor(name: string) { super(name) diff --git a/src/builders/fields/ChecklistFieldBuilder.ts b/src/builders/fields/ChecklistFieldBuilder.ts index ea06340..24597e1 100644 --- a/src/builders/fields/ChecklistFieldBuilder.ts +++ b/src/builders/fields/ChecklistFieldBuilder.ts @@ -1,8 +1,8 @@ import BaseOptionFieldBuilder from '@/builders/base/BaseOption' import { ChecklistField } from '@/resources/types/field/fields' -export default class ChecklistFieldBuilder extends BaseOptionFieldBuilder { - protected __default__: string[] = [] +export default class ChecklistFieldBuilder extends BaseOptionFieldBuilder { + __default__: string[] = [] constructor(name: string) { super('checklist', name) diff --git a/src/builders/fields/TextFieldBuilder.ts b/src/builders/fields/TextFieldBuilder.ts index b532421..91c7197 100644 --- a/src/builders/fields/TextFieldBuilder.ts +++ b/src/builders/fields/TextFieldBuilder.ts @@ -2,7 +2,7 @@ import { IBaseFieldBuilder } from '@/builders/base' import type { TextField } from '@/resources/types/field/fields' import BaseInputBuilder from '@/builders/base/BaseInput' -export interface ITextFieldBuilder extends IBaseFieldBuilder { +export interface ITextFieldBuilder extends IBaseFieldBuilder { placeholder (value: string): this autoComplete (value?: boolean): this } @@ -10,8 +10,8 @@ export interface ITextFieldBuilder extends IBaseFieldBuilder { /** * Field builder for the text field. */ -export default class TextFieldBuilder extends BaseInputBuilder implements ITextFieldBuilder { - protected __default__: string = '' +export default class TextFieldBuilder extends BaseInputBuilder implements ITextFieldBuilder { + __default__: string = '' constructor(name: string) { super(name) diff --git a/src/builders/index.ts b/src/builders/index.ts index 6e20921..0fb0bb6 100644 --- a/src/builders/index.ts +++ b/src/builders/index.ts @@ -1,16 +1,14 @@ -import GroupBuilder from '@/builders/group' -import { SchemaBuilder } from '@/builders/schema' +import { SchemaBuilder, SchemaModel } from '@/builders/schema' import { BaseFieldBuilder } from '@/builders/base' import TextFieldBuilder from '@/builders/fields/TextFieldBuilder' import CheckboxFieldBuilder from '@/builders/fields/CheckboxFieldBuilder' import ChecklistFieldBuilder from '@/builders/fields/ChecklistFieldBuilder' +import ButtonFieldBuilder from '@/builders/fields/ButtonFieldBuilder' +import NumberFieldBuilder from '@/builders/fields/NumberFieldBuilder' export const f = { - group: (legend?: string, ...fields: BaseFieldBuilder[])=> { - return new GroupBuilder(fields, legend) - }, /** * Returns a form schema builder instance. * Takes a model object as the first argument. Takes field builders and group builders as the rest of the arguments. @@ -38,7 +36,7 @@ export const f = { */ schema: >>( schema: S, - defaults?: Partial> + defaults?: Partial> ): SchemaBuilder => new SchemaBuilder(schema, defaults), /** * Create a text field builder instance. @@ -54,6 +52,13 @@ export const f = { text: (name: string): TextFieldBuilder => { return new TextFieldBuilder(name) }, + /** + * Create a button field builder instance. + * @param name - Name of the button. + */ + button: (name: string): ButtonFieldBuilder => { + return new ButtonFieldBuilder(name) + }, /** * Create a checkbox field builder instance. * @example @@ -79,6 +84,6 @@ export const f = { * @param name * @returns A checkbox field builder instance. */ - checklist: (name: string): ChecklistFieldBuilder => new ChecklistFieldBuilder(name) + checklist: (name: string): ChecklistFieldBuilder => new ChecklistFieldBuilder(name), + number: (name: string): NumberFieldBuilder => new NumberFieldBuilder(name) } - diff --git a/src/builders/schema.ts b/src/builders/schema.ts index 8fe05e1..0cba80c 100644 --- a/src/builders/schema.ts +++ b/src/builders/schema.ts @@ -1,26 +1,55 @@ import { BaseFieldBuilder } from '@/builders/base' import { FormGeneratorSchema } from '@/resources/types/generic' import { type Ref, ref } from 'vue' +import { NoValueError } from '@/exceptions' + +/** + * Type representing the model of a schema. + */ +export type SchemaModel< + S extends Record> +> = { + [K in keyof S]: ReturnType +} + +/** + * Type representing the `field` type of a built schema. + */ +export type BuiltSchemaField< + S extends Record> +> = + S[keyof S] extends BaseFieldBuilder + ? F + : never + /** * Form schema builder - * @param schema - An object containing field builders. The key of the object is the model key of the field, the value is the field builder instance. + * @param schema - An object containing field builders. The key of the object is the model key of the field, + * the value is the field builder instance. */ -export class SchemaBuilder >> { - protected model: Record = {} - protected fields: BaseFieldBuilder[] = [] +export class SchemaBuilder < + S extends Record> +> { + protected model: SchemaModel = {} as SchemaModel + protected fields: BaseFieldBuilder[] = [] - constructor(schema: S, defaults?: Partial>) { + constructor(schema: S, defaults?: Partial>) { + this.initialize(schema, defaults) + } + + /** + * Initialize the model and associated fields. + * @param schema - Schema object of field builders. + * @param defaults - Default values for the fields. + * @private + */ + private initialize (schema: S, defaults?: Partial>): void { for (const [ modelKey, field ] of Object.entries(schema) ) { try { - this.model[modelKey] = field.getInitialValue() - const addField = field.model(modelKey) - if (defaults && defaults[modelKey]) { - addField.default(defaults[modelKey]) - } - this.field(addField) + this.initializeField(modelKey as keyof S, field, defaults) } catch (e) { - if (typeof field.getInitialValue !== 'function') { + if (typeof field.getDefaultValue !== 'function') { throw new Error(`Error initializing field ${modelKey}: Field might not be a field builder instance.`) } throw new Error(`Error initializing field ${modelKey}: ${e}`) @@ -28,11 +57,40 @@ export class SchemaBuilder >> { } } + /** + * Initialize a single field. + * @param modelKey - Key for the model. + * @param field - Field builder instance associated with the key. + * @param defaults - Default values for fields. + * @private + */ + private initializeField ( + modelKey: keyof S, + field: BaseFieldBuilder, + defaults?: Partial> + ): void { + try { + this.model[modelKey] = field.getDefaultValue() + const addField = field.model(modelKey as string) + if (defaults?.[modelKey]) { + addField.default(defaults[modelKey]) + } + this.addField(addField) + } catch (e) { + // Thrown whenever a field builder does not emit values, such as with FieldButton. + if (e instanceof NoValueError) { + this.addField(field) + } else { + throw e + } + } + } + /** * Get an array of all fields. * @returns An array of fields. */ - getFields (): BaseFieldBuilder[] { + getFields (): BaseFieldBuilder[] { return this.fields } @@ -40,7 +98,7 @@ export class SchemaBuilder >> { * Add a field to the schema. * @param field - Field builder instance of the field that needs to be added. */ - field (field: BaseFieldBuilder): SchemaBuilder { + private addField > (field: F): SchemaBuilder { this.fields.push(field) return this } @@ -49,19 +107,24 @@ export class SchemaBuilder >> { * Build the schema and return as a Ref. * @returns Built JSON schema wrapped in a Ref. */ - toRef (): Ref { - return ref(this.build()) + toRef (): Ref< + FormGeneratorSchema, BuiltSchemaField> + > { + return ref(this.build()) as Ref, BuiltSchemaField>> } /** * Build the schema. Converts all builders to JSON. * @returns A JSON object representing the schema. */ - build (): FormGeneratorSchema { + build (): FormGeneratorSchema< + SchemaModel, + BuiltSchemaField + > { return { model: this.model, schema: { - fields: this.fields?.map((field: BaseFieldBuilder) => field.build()) ?? undefined + fields: this.fields?.map(field => field.build()) ?? undefined } } } diff --git a/src/exceptions.ts b/src/exceptions.ts new file mode 100644 index 0000000..1a2ab6a --- /dev/null +++ b/src/exceptions.ts @@ -0,0 +1,11 @@ +/** + * Custom error class representing a situation where a field is expected to have no value. + * @extends Error + */ +export class NoValueError extends Error { + + constructor() { + super('Field does not emit value(s).') + } + +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 7e8a4b7..85893fc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -71,6 +71,7 @@ export { validators } +export { NoValueError } from '@/exceptions' export { BaseFieldBuilder, type IBaseFieldBuilder } from '@/builders/base' export { f } from '@/builders/index' diff --git a/src/resources/types/generic.ts b/src/resources/types/generic.ts index 56f7901..3f5a533 100644 --- a/src/resources/types/generic.ts +++ b/src/resources/types/generic.ts @@ -29,11 +29,15 @@ export type FormGeneratorGroup = { fields: Field[]; } -export type FormGeneratorSchema = { - model: FormModel; +export type FormGeneratorSchema< + M extends FormModel = FormModel, + F extends Field = Field, + G extends FormGeneratorGroup = FormGeneratorGroup +> = { + model: M; schema: { - fields?: Field[]; - groups?: FormGeneratorGroup[]; + fields?: F[]; + groups?: G[]; }, } diff --git a/tests/builders/AbstractBaseBuilder.ts b/tests/builders/AbstractBaseBuilder.spec.ts similarity index 100% rename from tests/builders/AbstractBaseBuilder.ts rename to tests/builders/AbstractBaseBuilder.spec.ts diff --git a/tests/builders/BaseFieldBuilder.spec.ts b/tests/builders/BaseFieldBuilder.spec.ts index b7889fa..e7d4c17 100644 --- a/tests/builders/BaseFieldBuilder.spec.ts +++ b/tests/builders/BaseFieldBuilder.spec.ts @@ -8,7 +8,9 @@ type TestField = FieldBase & { testKey: string } -class TestBaseFieldBuilder extends BaseFieldBuilder { +class TestBaseFieldBuilder extends BaseFieldBuilder { + __default__: string = '' + constructor(name: string) { super('test', name) } diff --git a/tests/builders/SchemaBuilder.spec.ts b/tests/builders/SchemaBuilder.spec.ts index cd256da..452d5d8 100644 --- a/tests/builders/SchemaBuilder.spec.ts +++ b/tests/builders/SchemaBuilder.spec.ts @@ -16,8 +16,7 @@ describe('SchemaBuilder', () => { const builder = new SchemaBuilder({ testField: new TextFieldBuilder('testField'), testField2: new TextFieldBuilder('testField2') - } - ) + }) expect(builder.getFields()).toHaveLength(2) const onlyBuilders = builder.getFields().filter(field => field instanceof TextFieldBuilder) expect(onlyBuilders).toHaveLength(2) @@ -25,18 +24,6 @@ describe('SchemaBuilder', () => { }) - describe('field()', () => { - - it('Should add a field builder to the schema', () => { - const builder = getBuilderNoGroups() - expect(builder.getFields().length).toBe(1) - builder.field(new TextFieldBuilder('name2').model('model2')) - expect(builder.getFields().length).toBe(2) - expect(builder.getFields()[1].__data__().name).toBe('name2') - }) - - }) - describe('toRef()', () => { it('Should return a ref', () => { From 4db22f608844a4def1403847824711d0453352c8 Mon Sep 17 00:00:00 2001 From: kevinkosterr Date: Wed, 17 Dec 2025 22:02:34 +0100 Subject: [PATCH 15/15] WIP: add button field builder --- src/builders/fields/ButtonFieldBuilder.ts | 43 +++++++++++++++ tests/builders/ButtonFieldBuilder.spec.ts | 64 +++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 src/builders/fields/ButtonFieldBuilder.ts create mode 100644 tests/builders/ButtonFieldBuilder.spec.ts diff --git a/src/builders/fields/ButtonFieldBuilder.ts b/src/builders/fields/ButtonFieldBuilder.ts new file mode 100644 index 0000000..be58a5e --- /dev/null +++ b/src/builders/fields/ButtonFieldBuilder.ts @@ -0,0 +1,43 @@ +import { AbstractBaseBuilder, type IAbstractBaseBuilder } from '@/builders/base' +import type { ButtonField, Field } from '@/resources/types/field/fields' +import { NoValueError } from '@/exceptions' + +export interface IButtonFieldBuilder extends IAbstractBaseBuilder { + buttonText (text: string): this + onClick (callback: ButtonField['onClick']): this +} + +export default class ButtonFieldBuilder extends AbstractBaseBuilder implements IButtonFieldBuilder { + + constructor(name: string) { + super('button', name) + } + + getDefaultValue (): this['__default__'] { + throw new NoValueError() + } + + protected getRequiredKeys(): (keyof ButtonField)[] { + return [ 'type', 'buttonText', 'onClick' ] + } + + /** + * Set the text for the button. + * @param text - Text to set. + */ + buttonText (text: string): this { + this.data.buttonText = text + return this + } + + /** + * Set the onClick handler for the button. + * @param callback - Callback function. + */ + onClick (callback: ButtonField['onClick']): this { + this.data.onClick = callback + return this + } + +} + diff --git a/tests/builders/ButtonFieldBuilder.spec.ts b/tests/builders/ButtonFieldBuilder.spec.ts new file mode 100644 index 0000000..eee3343 --- /dev/null +++ b/tests/builders/ButtonFieldBuilder.spec.ts @@ -0,0 +1,64 @@ +import { it, describe, expect } from 'vitest' +import ButtonFieldBuilder from '@/builders/fields/ButtonFieldBuilder' +import { NoValueError } from '@/exceptions' + +const getBuilder = () => new ButtonFieldBuilder('testField') + +describe('ButtonFieldBuilder', () => { + + describe('constructor', () => { + + it('Should instantiate with the right type', () => { + const builder = new ButtonFieldBuilder('testField') + expect(builder.__data__().type).toBe('button') + }) + + it('Should instantiate with the right name', () => { + const builder = new ButtonFieldBuilder('testName') + expect(builder.__data__().name).toBe('testName') + }) + + }) + + describe('getDefaultValue()', () => { + + it('Should throw NoValueError', () => { + expect(() => { + getBuilder().getDefaultValue() + }).toThrowError(NoValueError) + }) + + }) + + describe('buttonText()', () => { + + it('Should properly set buttonText property', () => { + const builder = getBuilder() + builder.buttonText('Test text') + expect(builder.__data__().buttonText).toBe('Test text') + }) + + it('Should return builder instance', () => { + const builder = getBuilder().buttonText('Test text') + expect(builder).toBeInstanceOf(ButtonFieldBuilder) + }) + + }) + + describe('onClick()', () => { + + it('Should properly set onClick property', () => { + const fn = (model, field) => console.log(model, field) + const builder = getBuilder() + builder.onClick(fn) + expect(builder.__data__().onClick).toBe(fn) + }) + + it('Should return builder instance', () => { + const builder = getBuilder().onClick(() => {}) + expect(builder).toBeInstanceOf(ButtonFieldBuilder) + }) + + }) + +})