diff --git a/apps/demo/.gitignore b/apps/demo/.gitignore new file mode 100644 index 000000000..88feeab14 --- /dev/null +++ b/apps/demo/.gitignore @@ -0,0 +1,44 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +/public/admin +/public/admin.html + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/apps/demo/README.md b/apps/demo/README.md new file mode 100644 index 000000000..e215bc4cc --- /dev/null +++ b/apps/demo/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/apps/demo/components.json b/apps/demo/components.json new file mode 100644 index 000000000..edcaef267 --- /dev/null +++ b/apps/demo/components.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} +} diff --git a/apps/demo/content/pages/array.json b/apps/demo/content/pages/array.json new file mode 100644 index 000000000..02ad670b9 --- /dev/null +++ b/apps/demo/content/pages/array.json @@ -0,0 +1,53 @@ +{ + "_id": "363hSAskC1OdY74vN9YkhBSIAzU", + "_type": "Form", + "_index": "a0l", + "title": "Array", + "description": "", + "form": { + "schema": { + "type": "object", + "properties": { + "test": { + "type": "array", + "title": "Test", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "lastname": { + "type": "string", + "title": "Lastname" + }, + "project-specific": { + "type": "string", + "title": "Project specific", + "format": "data-url" + } + } + } + }, + "bla": { + "type": "string", + "title": "Bla", + "format": "data-url" + } + } + }, + "ui": { + "test": { + "test": { + "project-specific": { + "ui:widget": "my-custom-widget" + } + } + }, + "bla": { + "ui:widget": "my-custom-widget" + } + } + } +} \ No newline at end of file diff --git a/apps/demo/content/pages/arrays.json b/apps/demo/content/pages/arrays.json new file mode 100644 index 000000000..a11c02597 --- /dev/null +++ b/apps/demo/content/pages/arrays.json @@ -0,0 +1,205 @@ +{ + "_id": "34n5maI5YJYxknkKItzFm26Mgbr", + "_type": "Form", + "_index": "a2", + "title": "Arrays", + "description": "", + "form": { + "schema": { + "definitions": { + "Thing": { + "type": "object", + "properties": { + "name": { + "type": "string", + "default": "Default name" + } + } + } + }, + "type": "object", + "properties": { + "listOfStrings": { + "type": "array", + "title": "A list of strings", + "items": { + "type": "string", + "default": "bazinga" + } + }, + "multipleChoicesList": { + "type": "array", + "title": "A multiple choices list", + "items": { + "type": "string", + "enum": [ + "foo", + "bar", + "fuzz", + "qux" + ] + }, + "uniqueItems": true + }, + "fixedItemsList": { + "type": "array", + "title": "A list of fixed items", + "items": [ + { + "title": "A string value", + "type": "string", + "default": "lorem ipsum" + }, + { + "title": "a boolean value", + "type": "boolean" + } + ], + "additionalItems": { + "title": "Additional item", + "type": "number" + } + }, + "minItemsList": { + "type": "array", + "title": "A list with a minimal number of items", + "minItems": 3, + "items": { + "$ref": "#/definitions/Thing" + } + }, + "defaultsAndMinItems": { + "type": "array", + "title": "List and item level defaults", + "minItems": 5, + "default": [ + "carp", + "trout", + "bream" + ], + "items": { + "type": "string", + "default": "unidentified" + } + }, + "nestedList": { + "type": "array", + "title": "Nested list", + "items": { + "type": "array", + "title": "Inner list", + "items": { + "type": "string", + "default": "lorem ipsum" + } + } + }, + "unorderable": { + "title": "Unorderable items", + "type": "array", + "items": { + "type": "string", + "default": "lorem ipsum" + } + }, + "copyable": { + "title": "Copyable items", + "type": "array", + "items": { + "type": "string", + "default": "lorem ipsum" + } + }, + "unremovable": { + "title": "Unremovable items", + "type": "array", + "items": { + "type": "string", + "default": "lorem ipsum" + } + }, + "noToolbar": { + "title": "No add, remove and order buttons", + "type": "array", + "items": { + "type": "string", + "default": "lorem ipsum" + } + }, + "fixedNoToolbar": { + "title": "Fixed array without buttons", + "type": "array", + "items": [ + { + "title": "A number", + "type": "number", + "default": 42 + }, + { + "title": "A boolean", + "type": "boolean", + "default": false + } + ], + "additionalItems": { + "title": "A string", + "type": "string", + "default": "lorem ipsum" + } + } + } + }, + "ui": { + "listOfStrings": { + "items": { + "ui:emptyValue": "" + } + }, + "multipleChoicesList": { + "ui:widget": "checkboxes" + }, + "fixedItemsList": { + "items": [ + { + "ui:widget": "textarea" + }, + { + "ui:widget": "select" + } + ], + "additionalItems": { + "ui:widget": "updown" + } + }, + "unorderable": { + "ui:options": { + "orderable": false + } + }, + "copyable": { + "ui:options": { + "copyable": true + } + }, + "unremovable": { + "ui:options": { + "removable": false + } + }, + "noToolbar": { + "ui:options": { + "addable": false, + "orderable": false, + "removable": false + } + }, + "fixedNoToolbar": { + "ui:options": { + "addable": false, + "orderable": false, + "removable": false + } + } + } + } +} \ No newline at end of file diff --git a/apps/demo/content/pages/basic-form.json b/apps/demo/content/pages/basic-form.json new file mode 100644 index 000000000..06bbb4b54 --- /dev/null +++ b/apps/demo/content/pages/basic-form.json @@ -0,0 +1,77 @@ +{ + "_id": "34SuSRdmN52pNxrySIyFTBdYn0H", + "_type": "Form", + "_index": "a1", + "title": "Basic form", + "description": "Simple form fields", + "form": { + "schema": { + "title": "A registration form", + "description": "A simple form example.", + "type": "object", + "required": ["firstName", "lastName"], + "properties": { + "firstName": { + "type": "string", + "title": "First name", + "default": "Chuck" + }, + "lastName": { + "type": "string", + "title": "Last name" + }, + "age": { + "type": "integer", + "title": "Age", + "minimum": 18 + }, + "bio": { + "type": "string", + "title": "Bio" + }, + "password": { + "type": "string", + "title": "Password", + "minLength": 3 + }, + "telephone": { + "type": "string", + "title": "Telephone", + "minLength": 10 + } + } + }, + "ui": { + "firstName": { + "ui:autofocus": true, + "ui:emptyValue": "", + "ui:placeholder": "ui:emptyValue causes this field to always be valid despite being required", + "ui:autocomplete": "family-name", + "ui:enableMarkdownInDescription": true, + "ui:description": "Make text **bold** or *italic*. Take a look at other options [here](https://markdown-to-jsx.quantizor.dev/)." + }, + "lastName": { + "ui:autocomplete": "given-name", + "ui:enableMarkdownInDescription": true, + "ui:description": "Make things **bold** or *italic*. Embed snippets of `code`. NOTE: Unsafe HTML, not rendered " + }, + "age": { + "ui:widget": "updown", + "ui:title": "Age of person", + "ui:description": "(earth year)" + }, + "bio": { + "ui:widget": "textarea" + }, + "password": { + "ui:widget": "password", + "ui:help": "Hint: Make it strong!" + }, + "telephone": { + "ui:options": { + "inputType": "tel" + } + } + } + } +} diff --git a/apps/demo/content/pages/basic-input-field.json b/apps/demo/content/pages/basic-input-field.json new file mode 100644 index 000000000..8b0ea2d96 --- /dev/null +++ b/apps/demo/content/pages/basic-input-field.json @@ -0,0 +1,34 @@ +{ + "_id": "361JxeCk2GXIXYIMyyy6T0sLGVt", + "_type": "Form", + "_index": "Zz", + "title": "Basic input field", + "description": "", + "form": { + "schema": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "textarea": { + "type": "string", + "title": "Text area" + }, + "email": { + "type": "string", + "title": "Email input" + } + } + }, + "ui": { + "name": { + "ui:placeholder": "Your name" + }, + "textarea": { + "ui:widget": "textarea" + }, + "email": {} + } + } +} \ No newline at end of file diff --git a/apps/demo/content/pages/file-field.json b/apps/demo/content/pages/file-field.json new file mode 100644 index 000000000..1bc77c99b --- /dev/null +++ b/apps/demo/content/pages/file-field.json @@ -0,0 +1,24 @@ +{ + "_id": "363QRkgQVIswyZ3Z6V8vyax4JS2", + "_type": "Form", + "_index": "a0V", + "title": "File field", + "description": "", + "form": { + "schema": { + "type": "object", + "properties": { + "test": { + "type": "string", + "title": "Test", + "format": "data-url" + } + } + }, + "ui": { + "test": { + "ui:widget": "my-custom-widget" + } + } + } +} \ No newline at end of file diff --git a/apps/demo/content/pages/nested.json b/apps/demo/content/pages/nested.json new file mode 100644 index 000000000..0c894dd9b --- /dev/null +++ b/apps/demo/content/pages/nested.json @@ -0,0 +1,82 @@ +{ + "_id": "34yTHHpc2Bo0sqmevyVuMWEUgwk", + "_type": "Form", + "_index": "a4", + "title": "Nested", + "description": "", + "form": { + "schema": { + "title": "A list of tasks", + "type": "object", + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string", + "title": "Task list title" + }, + "tasks": { + "type": "array", + "title": "Tasks", + "items": { + "type": "object", + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string", + "title": "Title", + "description": "A sample title" + }, + "details": { + "type": "string", + "title": "Task details", + "description": "Enter the task details" + }, + "done": { + "type": "boolean", + "title": "Done?", + "default": false + } + } + } + } + } + }, + "ui": { + "title": { + "ui:placeholder": "Enter a title for your task list", + "ui:className": "font-semibold" + }, + "tasks": { + "ui:description": "Add your tasks below", + "items": { + "title": { + "ui:placeholder": "What needs to be done?", + "ui:className": "font-semibold" + }, + "details": { + "ui:widget": "textarea", + "ui:placeholder": "Additional details about this task...", + "ui:options": { + "rows": 3 + } + }, + "done": { + "ui:widget": "checkbox", + "ui:options": { + "label": false + } + }, + "ui:order": [ + "title", + "details", + "done" + ] + } + } + } + } +} \ No newline at end of file diff --git a/apps/demo/content/pages/numbers.json b/apps/demo/content/pages/numbers.json new file mode 100644 index 000000000..f8e52ed1a --- /dev/null +++ b/apps/demo/content/pages/numbers.json @@ -0,0 +1,71 @@ +{ + "_id": "34n61xdCt948cA108Lw04RleQgp", + "_type": "Form", + "_index": "a3", + "title": "Numbers", + "description": "", + "form": { + "schema": { + "type": "object", + "title": "Number fields & widgets", + "properties": { + "number": { + "title": "Number", + "type": "number" + }, + "integer": { + "title": "Integer", + "type": "integer" + }, + "numberEnum": { + "type": "number", + "title": "Number enum", + "enum": [ + 1, + 2, + 3 + ] + }, + "numberEnumRadio": { + "type": "number", + "title": "Number enum", + "enum": [ + 1, + 2, + 3 + ] + }, + "integerRange": { + "title": "Integer range", + "type": "integer", + "minimum": -50, + "maximum": 50 + }, + "integerRangeSteps": { + "title": "Integer range (by 10)", + "type": "integer", + "minimum": 50, + "maximum": 100, + "multipleOf": 10 + } + } + }, + "ui": { + "integer": { + "ui:widget": "updown" + }, + "numberEnumRadio": { + "ui:widget": "radio", + "ui:options": { + "inline": true + } + }, + "integerRange": { + "ui:widget": "range" + }, + "integerRangeSteps": { + "ui:widget": "range" + } + } + } +} \ No newline at end of file diff --git a/apps/demo/content/pages/select-field.json b/apps/demo/content/pages/select-field.json new file mode 100644 index 000000000..c9525b769 --- /dev/null +++ b/apps/demo/content/pages/select-field.json @@ -0,0 +1,56 @@ +{ + "_id": "360wsxfTZnSJQzOm32QcoKfJpWu", + "_type": "Form", + "_index": "a0", + "title": "Select field", + "description": "", + "form": { + "schema": { + "type": "object", + "properties": { + "color-radio": { + "type": "string", + "title": "Color as radio", + "oneOf": [ + { + "const": "red", + "title": "Red" + }, + { + "const": "blue", + "title": "Blue" + }, + { + "const": "green", + "title": "Green" + } + ] + }, + "color-as-dropdown": { + "type": "string", + "title": "Color as dropdown", + "oneOf": [ + { + "const": "red", + "title": "Red" + }, + { + "const": "blue", + "title": "Blue" + } + ] + } + } + }, + "ui": { + "color-radio": { + "ui:placeholder": "", + "ui:widget": "radio" + }, + "color-as-dropdown": { + "ui:placeholder": "", + "ui:widget": "select" + } + } + } +} \ No newline at end of file diff --git a/apps/demo/content/pages/single-input.json b/apps/demo/content/pages/single-input.json new file mode 100644 index 000000000..469fd99de --- /dev/null +++ b/apps/demo/content/pages/single-input.json @@ -0,0 +1,19 @@ +{ + "_id": "36469OLDe2mleYkwDQ0wohti56D", + "_type": "Form", + "_index": "a5", + "title": "Single input", + "description": "", + "form": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Name" + } + } + }, + "ui": {} + } +} \ No newline at end of file diff --git a/apps/demo/next.config.ts b/apps/demo/next.config.ts new file mode 100644 index 000000000..e9ffa3083 --- /dev/null +++ b/apps/demo/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + /* config options here */ +}; + +export default nextConfig; diff --git a/apps/demo/package.json b/apps/demo/package.json new file mode 100644 index 000000000..b5e9ad816 --- /dev/null +++ b/apps/demo/package.json @@ -0,0 +1,35 @@ +{ + "name": "@alinea/demo", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "bun run --cwd ../.. build && node ../../dist/cli.js build -- next build", + "start": "next start" + }, + "dependencies": { + "@rjsf/core": "6.1.2", + "@rjsf/daisyui": "6.1.2", + "@rjsf/utils": "6.1.2", + "@rjsf/validator-ajv8": "6.1.2", + "@types/better-sqlite3": "^7.6.13", + "better-sqlite3": "^12.4.1", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.546.0", + "next": "=15.4.2", + "react": "=19.2.0", + "react-dom": "=19.2.0", + "tailwind-merge": "^3.3.1", + "tw-animate-css": "^1.4.0" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "daisyui": "^5.3.8", + "tailwindcss": "^4.1.16", + "typescript": "^5" + } +} diff --git a/apps/demo/postcss.config.mjs b/apps/demo/postcss.config.mjs new file mode 100644 index 000000000..849d665d6 --- /dev/null +++ b/apps/demo/postcss.config.mjs @@ -0,0 +1,7 @@ +const config = { + plugins: { + '@tailwindcss/postcss': {} + } +} + +export default config diff --git a/apps/demo/public/file.svg b/apps/demo/public/file.svg new file mode 100644 index 000000000..004145cdd --- /dev/null +++ b/apps/demo/public/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/demo/public/globe.svg b/apps/demo/public/globe.svg new file mode 100644 index 000000000..567f17b0d --- /dev/null +++ b/apps/demo/public/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/demo/public/next.svg b/apps/demo/public/next.svg new file mode 100644 index 000000000..5174b28c5 --- /dev/null +++ b/apps/demo/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/demo/public/vercel.svg b/apps/demo/public/vercel.svg new file mode 100644 index 000000000..770539603 --- /dev/null +++ b/apps/demo/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/demo/public/window.svg b/apps/demo/public/window.svg new file mode 100644 index 000000000..b2b2a44f6 --- /dev/null +++ b/apps/demo/public/window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/demo/src/Form.schema.ts b/apps/demo/src/Form.schema.ts new file mode 100644 index 000000000..8250d6b21 --- /dev/null +++ b/apps/demo/src/Form.schema.ts @@ -0,0 +1,21 @@ +import {Config, Field} from 'alinea' +import {FormFileField} from 'alinea/field/form/base/FormFileField' +import {FormSelectField} from 'alinea/field/form/base/FormSelectField' +import {FormTextField} from 'alinea/field/form/base/FormTextField' +import {MyField} from './project-specific-field/MyField' + +export const Form = Config.type('Form', { + fields: { + title: Field.text('Title'), + path: Field.path('Path'), + description: Field.text('Description', {multiline: true}), + form: Field.form('Form', { + baseFields: { + MyField, + FormFileField, + FormSelectField, + FormTextField + } + }) + } +}) diff --git a/apps/demo/src/app/[form]/ThemedForm.tsx b/apps/demo/src/app/[form]/ThemedForm.tsx new file mode 100644 index 000000000..e006aa26f --- /dev/null +++ b/apps/demo/src/app/[form]/ThemedForm.tsx @@ -0,0 +1,220 @@ +/** biome-ignore-all lint/correctness/noUnusedVariables: used to prevent certain parameters from being passed to the DOM */ +'use client' + +import DaisyUIForm from '@rjsf/daisyui' +import { + type ArrayFieldTitleProps, + type DescriptionFieldProps, + type FieldErrorProps, + getSubmitButtonOptions, + type IconButtonProps, + type ObjectFieldTemplateProps, + type SubmitButtonProps, + type TitleFieldProps, + titleId +} from '@rjsf/utils' +import validator from '@rjsf/validator-ajv8' +import type {Infer} from 'alinea' +import {cx} from 'class-variance-authority' +import {MoveDown, MoveUp, Trash2Icon, TriangleAlert} from 'lucide-react' +import {type ReactNode, startTransition, useActionState, useState} from 'react' +import type {Form} from '@/Form.schema' +import {MyFieldWidget} from '../../project-specific-field/MyFieldWidget' +import {handleRjsfSubmit, type RjsfFormState} from './handleRjsfSubmit.action' + +function TitleFieldTemplate(props: TitleFieldProps) { + const {id, required, title} = props + + console.log(props) + return ( +
+ {title} + {required && *} +
+ ) +} + +function ArrayFieldTitleTemplate( + props: ArrayFieldTitleProps & {fieldPathId: any} +) { + const {title, fieldPathId} = props + const id = titleId(fieldPathId) + return ( +

+ {title} +

+ ) +} + +function AddButton(props: IconButtonProps) { + const {icon, iconType, uiSchema, popover, ...btnProps} = props + return ( + + ) +} + +function MoveUpButton(props: IconButtonProps) { + const {icon, iconType, uiSchema, popover, ...btnProps} = props + return ( + + ) +} + +function MoveDownButton(props: IconButtonProps) { + const {icon, iconType, uiSchema, popover, ...btnProps} = props + return ( + + ) +} + +function RemoveButton(props: IconButtonProps) { + const {icon, iconType, uiSchema, popover, ...btnProps} = props + return ( + + ) +} + +function SubmitButton(props: SubmitButtonProps) { + const {uiSchema} = props + const {norender} = getSubmitButtonOptions(uiSchema) + if (norender) { + return null + } + return ( + + ) +} + +function ObjectFieldTemplate(props: ObjectFieldTemplateProps) { + return ( +
+ {props.title &&
{props.title}
} + {props.description as string} + {props.properties.map(element => ( +
+ {element.content as any} +
+ ))} +
+ ) +} + +function FieldErrorTemplate(props: FieldErrorProps) { + const {errors} = props + + if (!errors) return null + + let errorString = '' + + if (typeof errors === 'string') { + errorString = errors + } else if (Array.isArray(errors)) { + errorString = errors.join(', ') + } else { + errorString = `${errors}` + } + return ( +
+ + {errorString} +
+ ) +} + +export const ThemedForm: React.FC<{ + page: Infer.Entry +}> = ({page}) => { + const [theme, setTheme] = useState('light') + const {schema, ui} = page.form + const [state, dispatch, isPending] = useActionState( + handleRjsfSubmit, + { + key: 'initial', + formId: page._id + } + ) + const onSubmit = (formData: any) => { + console.log('Form submitted', formData) + startTransition(() => { + dispatch(formData) + }) + } + + if (state.key === 'submitted') { + console.log(state.submissions) + return ( +
+ Thank you for your submission! + {state.submissions?.length && + state.submissions.map((submission, index) => ( +
+              {submission.id}: 
+ {submission.data} +
+ ))} +
+ ) + } + + return ( +
+ + { + onSubmit(formData) + event.preventDefault() + }} + schema={schema} + uiSchema={ui!} + widgets={{ + 'my-custom-widget': MyFieldWidget + }} + templates={{ + ArrayFieldTitleTemplate, + TitleFieldTemplate, + ObjectFieldTemplate, + FieldErrorTemplate, + ButtonTemplates: { + AddButton, + MoveUpButton, + MoveDownButton, + RemoveButton, + SubmitButton + } as any + }} + showErrorList="bottom" + > + + +
+ ) +} diff --git a/apps/demo/src/app/[form]/handleRjsfSubmit.action.ts b/apps/demo/src/app/[form]/handleRjsfSubmit.action.ts new file mode 100644 index 000000000..a4f7ef010 --- /dev/null +++ b/apps/demo/src/app/[form]/handleRjsfSubmit.action.ts @@ -0,0 +1,101 @@ +'use server' + +import validator from '@rjsf/validator-ajv8' +import {cms} from '@/cms' +import {Form} from '@/Form.schema' +import {getDb} from '@/lib/db' + +export type RjsfFormState = + | { + key: 'initial' + formId: string + } + | { + key: 'error' + formId: string + formData: any + errors: any + } + | { + key: 'submitted' + formId: string + submissions?: any[] + } + +export async function handleRjsfSubmit( + state: RjsfFormState, + formData: any +): Promise { + console.log('state', state) + console.log('formData', formData) + + const page = await cms.first({type: Form, id: state.formId}) + if (!page) return state + + // Validate data + + const validationData = validator.validateFormData( + formData, + page.form.schema, + undefined, + undefined, + page.form.ui + ) + + if (validationData.errors.length > 0) { + console.error(state, formData, validationData.errors) + return { + key: 'error', + formId: state.formId, + formData, + errors: validationData + } + } + + const db = getDb() + try { + await db + .prepare( + `INSERT INTO submissions (id, form_id, data) VALUES (@id, @form_id, @data)` + ) + .run({ + id: crypto.randomUUID(), + form_id: state.formId, + data: JSON.stringify(formData) + }) + } catch (error) { + console.error(error) + return { + key: 'error', + formId: state.formId, + formData, + errors: [ + { + name: 'dbStorageFailed', + property: 'general', + message: 'Failed writing data to the database. Please try again.', + stack: 'Failed writing data to the database. Please try again.' + } + ] + } + } + + try { + const db = getDb() + const submissions = await db + .prepare( + 'SELECT * FROM submissions WHERE form_id = @form_id ORDER BY inserted_at DESC' + ) + .all({form_id: state.formId}) + return { + key: 'submitted', + formId: state.formId, + submissions + } + } catch (e) {} + + return { + key: 'submitted', + formId: state.formId + } +} diff --git a/apps/demo/src/app/[form]/page.tsx b/apps/demo/src/app/[form]/page.tsx new file mode 100644 index 000000000..6966302e2 --- /dev/null +++ b/apps/demo/src/app/[form]/page.tsx @@ -0,0 +1,33 @@ +import Link from 'next/link' +import {notFound} from 'next/navigation.js' +import {cms} from '@/cms' +import {Form} from '@/Form.schema' +import {ThemedForm} from './ThemedForm' + +export default async function Home({ + params +}: { + params: Promise<{form: string}> +}) { + const {form: path} = await params + const page = await cms.first({type: Form, url: `/${path}`}) + if (!page) notFound() + + return ( +
+
+
+

+ {page?.title} +

+ + Back + +
+
+ +
+
+
+ ) +} diff --git a/apps/demo/src/app/api/cms/route.ts b/apps/demo/src/app/api/cms/route.ts new file mode 100644 index 000000000..3ac550a7b --- /dev/null +++ b/apps/demo/src/app/api/cms/route.ts @@ -0,0 +1,8 @@ +//import {db} from '@vercel/postgres' +import {createHandler} from 'alinea/next' +import {cms} from '@/cms' + +const handler = createHandler({cms}) + +export const GET = handler +export const POST = handler diff --git a/apps/demo/src/app/error.tsx b/apps/demo/src/app/error.tsx new file mode 100644 index 000000000..0072535b9 --- /dev/null +++ b/apps/demo/src/app/error.tsx @@ -0,0 +1,31 @@ +'use client' // Error boundaries must be Client Components + +import {useEffect} from 'react' + +export default function ErrorPage({ + error, + reset +}: { + error: Error & {digest?: string} + reset: () => void +}) { + useEffect(() => { + // Log the error to an error reporting service + console.error(error) + }, [error]) + + return ( +
+

Something went wrong!

+ +
+ ) +} diff --git a/apps/demo/src/app/favicon.ico b/apps/demo/src/app/favicon.ico new file mode 100644 index 000000000..718d6fea4 Binary files /dev/null and b/apps/demo/src/app/favicon.ico differ diff --git a/apps/demo/src/app/globals.css b/apps/demo/src/app/globals.css new file mode 100644 index 000000000..a1f4ddde3 --- /dev/null +++ b/apps/demo/src/app/globals.css @@ -0,0 +1,130 @@ +@import 'tailwindcss'; +@import 'tw-animate-css'; + +@source "../../../../node_modules/@rjsf/daisyui"; + +@plugin "daisyui" { + themes: all; +} + +/* @source inline("dropdown","btn","input","select","textarea","label","toggle","checkbox","radio","alert","card"); */ + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); +} + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/apps/demo/src/app/layout.tsx b/apps/demo/src/app/layout.tsx new file mode 100644 index 000000000..f08542d76 --- /dev/null +++ b/apps/demo/src/app/layout.tsx @@ -0,0 +1,34 @@ +import type {Metadata} from 'next' +import {Geist, Geist_Mono} from 'next/font/google' +import './globals.css' + +const geistSans = Geist({ + variable: '--font-geist-sans', + subsets: ['latin'] +}) + +const geistMono = Geist_Mono({ + variable: '--font-geist-mono', + subsets: ['latin'] +}) + +export const metadata: Metadata = { + title: 'Create Next App', + description: 'Generated by create next app' +} + +export default function RootLayout({ + children +}: Readonly<{ + children: React.ReactNode +}>) { + return ( + + + {children} + + + ) +} diff --git a/apps/demo/src/app/not-found.tsx b/apps/demo/src/app/not-found.tsx new file mode 100644 index 000000000..841cd7ca5 --- /dev/null +++ b/apps/demo/src/app/not-found.tsx @@ -0,0 +1,11 @@ +import Link from 'next/link' + +export default function NotFound() { + return ( +
+

Not Found

+

Could not find requested resource

+ Return Home +
+ ) +} diff --git a/apps/demo/src/app/page.tsx b/apps/demo/src/app/page.tsx new file mode 100644 index 000000000..595e4da48 --- /dev/null +++ b/apps/demo/src/app/page.tsx @@ -0,0 +1,36 @@ +import Link from 'next/link.js' +import {cms} from '@/cms' +import {Form} from '@/Form.schema' + +export default async function Home() { + const forms = await cms.find({type: Form}) + + return ( +
+
+
+

+ Form Builder Demo123 +

+

+ Explore different form implementations and capabilities +

+
+ +
+ {forms.map(form => { + return ( + + {form.title} + + ) + })} +
+
+
+ ) +} diff --git a/apps/demo/src/cms.ts b/apps/demo/src/cms.ts new file mode 100644 index 000000000..b4de4a420 --- /dev/null +++ b/apps/demo/src/cms.ts @@ -0,0 +1,41 @@ +import {Config, Field} from 'alinea' +import {createCMS} from 'alinea/next' +import {Form} from './Form.schema' + +export const cms = createCMS({ + // List out available types in your schema + schema: { + Form + }, + + // Define the content structure of your CMS + workspaces: { + main: Config.workspace('Form examples', { + preview: true, + source: 'content', + mediaDir: 'public', + roots: { + pages: Config.root('Forms', { + contains: ['Form'] + }), + media: Config.media() + } + }) + }, + + baseUrl: { + // Point to your local website + development: 'http://localhost:3000', + // The production URL of your website + production: 'https://alinea-blue.vercel.app/' + }, + + // Enable live previews after adding to your layout + // preview: true, + + // The handler route URL + handlerUrl: '/api/cms', + + // The admin dashboard will be bundled in this static file + dashboardFile: 'admin.html' +}) diff --git a/apps/demo/src/lib/db.ts b/apps/demo/src/lib/db.ts new file mode 100644 index 000000000..70ef0496c --- /dev/null +++ b/apps/demo/src/lib/db.ts @@ -0,0 +1,66 @@ +import Database from 'better-sqlite3' +import {randomUUID} from 'crypto' +import fs from 'fs' +import path from 'path' + +let db: Database.Database | null = null + +export function getDb(): Database.Database { + if (db) return db + + // Use the temporary filesystem (ephemeral on serverless) + const tmp = process.env.TMPDIR || '/tmp' + const file = path.join(tmp, 'demo.sqlite') + const firstTime = !fs.existsSync(file) + + db = new Database(file) + + if (firstTime) { + db.exec(` + CREATE TABLE IF NOT EXISTS submissions ( + id TEXT PRIMARY KEY, + form_id TEXT NOT NULL, + inserted_at DATETIME DEFAULT CURRENT_TIMESTAMP, + data JSON + ); + `) + console.log('Database initialized at', file) + } + + return db! +} + +export async function GET() { + const db = getDb() + const rows = db + .prepare('SELECT * FROM submissions ORDER BY inserted_at DESC') + .all() + return Response.json(rows) +} + +export async function POST(req: Request) { + const payload = await req.json() + const db = getDb() + + if (!payload.form_id) { + return Response.json( + {error: 'Missing required field: form_id'}, + {status: 400} + ) + } + + const submission = { + id: randomUUID(), + form_id: payload.form_id, + data: JSON.stringify(payload.data ?? {}) + } + + db.prepare( + `INSERT INTO submissions (id, form_id, data) VALUES (@id, @form_id, @data)` + ).run(submission) + + return Response.json({ + ok: true, + submission + }) +} diff --git a/apps/demo/src/lib/utils.ts b/apps/demo/src/lib/utils.ts new file mode 100644 index 000000000..bd0c391dd --- /dev/null +++ b/apps/demo/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/apps/demo/src/project-specific-field/MyField.tsx b/apps/demo/src/project-specific-field/MyField.tsx new file mode 100644 index 000000000..fd62a1fd8 --- /dev/null +++ b/apps/demo/src/project-specific-field/MyField.tsx @@ -0,0 +1,59 @@ +import type {Infer} from 'alinea/core/Infer' +import {type} from 'alinea/core/Type' +import {path} from 'alinea/field/path' +import {text} from 'alinea/field/text/TextField' +import type {JSONSchema7, JSONSchema7Definition} from 'json-schema' +import type { + FormDefinition, + FormFieldDefinition +} from '../../../../dist/field/form.js' + +const Schema = type('Project-specific-field', { + fields: { + title: text('Label', {required: true, width: 0.5}), + key: path('Key', {required: true, width: 0.5}) + } +}) + +function rjsfToField( + key: string, + schema: FormDefinition['schema'], + uiSchema: FormDefinition['ui'] +): Infer | undefined { + if (schema.type !== 'string') return undefined + const uiFieldSchema = uiSchema[key] || {} + if (uiFieldSchema?.['ui:widget'] !== 'my-custom-widget') return undefined + + return { + title: schema.title || '[No label]', + key: key + } +} + +function addFieldToRjsf( + properties: Record, + uiSchema: FormDefinition['ui'], + field: Infer.ListItem +) { + const key = field.key || field._id + + const schema: JSONSchema7Definition = { + type: 'string', + title: field.title || '[No label]', + format: 'data-url' + } + properties[key] = schema + + const ui: any = {} + ui['ui:widget'] = 'my-custom-widget' + + if (Object.keys(ui).length > 0) { + uiSchema[key] = ui + } +} + +export const MyField = { + schema: Schema, + rjsfToField, + addFieldToRjsf +} satisfies FormFieldDefinition diff --git a/apps/demo/src/project-specific-field/MyFieldWidget.tsx b/apps/demo/src/project-specific-field/MyFieldWidget.tsx new file mode 100644 index 000000000..0ac35c782 --- /dev/null +++ b/apps/demo/src/project-specific-field/MyFieldWidget.tsx @@ -0,0 +1,10 @@ +export const MyFieldWidget: React.FC<{ + label: string +}> = ({label}) => { + return ( +
+

{label}

+

My Custom Widget

+
+ ) +} diff --git a/apps/demo/tsconfig.json b/apps/demo/tsconfig.json new file mode 100644 index 000000000..34939b2a9 --- /dev/null +++ b/apps/demo/tsconfig.json @@ -0,0 +1,42 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": [ + "./src/*" + ] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts", + "**/*.mts" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/apps/dev/content/primary/fields/examples/form-field.json b/apps/dev/content/primary/fields/examples/form-field.json new file mode 100644 index 000000000..f6b359d46 --- /dev/null +++ b/apps/dev/content/primary/fields/examples/form-field.json @@ -0,0 +1,52 @@ +{ + "_id": "34Bj3ZsdjBWfsO5nyI5hjIuk44S", + "_type": "FormExample", + "_index": "Zz", + "title": "Form field", + "text": { + "schema": { + "properties": { + "firstname": { + "type": "string", + "title": "Firstname", + "default": "" + }, + "lastname": { + "type": "string", + "title": "Lastname", + "default": "" + }, + "lastname2": { + "type": "string", + "title": "Lastname 22", + "default": "" + }, + "email": { + "type": "string", + "format": "email", + "title": "Email" + } + } + }, + "ui": { + "firstname": { + "ui:placeholder": "Bill123" + }, + "lastname": { + "ui:placeholder": "Merckx" + }, + "lastname2": { + "ui:placeholder": "" + } + } + }, + "metadata": { + "title": "", + "description": "", + "openGraph": { + "image": {}, + "title": "", + "description": "" + } + } +} \ No newline at end of file diff --git a/apps/dev/src/schema/example/FormExample.tsx b/apps/dev/src/schema/example/FormExample.tsx new file mode 100644 index 000000000..6a9f03c31 --- /dev/null +++ b/apps/dev/src/schema/example/FormExample.tsx @@ -0,0 +1,7 @@ +import {Config, Field} from 'alinea' + +export const FormExample = Config.document('Form', { + fields: { + text: Field.form('Text field') + } +}) diff --git a/apps/dev/src/schema/example/index.ts b/apps/dev/src/schema/example/index.ts index 117b318fa..5c775bbb4 100644 --- a/apps/dev/src/schema/example/index.ts +++ b/apps/dev/src/schema/example/index.ts @@ -4,6 +4,7 @@ export * from './CustomFieldExample' export * from './CustomPageExample' export * from './CustomViewExample' export * from './ExtraTabExample' +export * from './FormExample' export * from './I18nFields' export * from './InlineFields' export * from './LayoutFields' @@ -11,5 +12,5 @@ export * from './LinkFields' export * from './ListFields' export * from './QuizExample' export * from './RichTextFields' -export * from './TabsExample' export * from './SharedFields' +export * from './TabsExample' diff --git a/bun.lock b/bun.lock index 484e1046e..707fd03a5 100644 --- a/bun.lock +++ b/bun.lock @@ -14,6 +14,9 @@ "@esbx/reporter": "^0.0.20", "@esbx/workspaces": "^0.0.20", "@ladle/react": "^4.1.2", + "@rjsf/core": "6.1.2", + "@rjsf/utils": "6.1.2", + "@rjsf/validator-ajv8": "6.1.2", "@types/bun": "^1.1.17", "@types/fs-extra": "^11.0.1", "@types/glob": "^7.1.4", @@ -30,8 +33,6 @@ "npm-run-all": "^4.1.5", "postcss-modules": "^6.0.0", "postcss-pxtorem": "^6.0.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", "sass": "^1.63.4", "sass-embedded": "^1.83.4", "sharp": "0.32.6", @@ -42,8 +43,37 @@ "yjs-src": "npm:yjs@13.6.11", }, "peerDependencies": { - "react": "*", - "react-dom": "*", + "react": "^19.2.0", + "react-dom": "^19.2.0", + }, + }, + "apps/demo": { + "name": "@alinea/demo", + "version": "0.1.0", + "dependencies": { + "@rjsf/core": "6.1.2", + "@rjsf/daisyui": "6.1.2", + "@rjsf/utils": "6.1.2", + "@rjsf/validator-ajv8": "6.1.2", + "@types/better-sqlite3": "^7.6.13", + "better-sqlite3": "^12.4.1", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.546.0", + "next": "=15.4.2", + "react": "=19.2.0", + "react-dom": "=19.2.0", + "tailwind-merge": "^3.3.1", + "tw-animate-css": "^1.4.0", + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "daisyui": "^5.3.8", + "tailwindcss": "^4.1.16", + "typescript": "^5", }, }, "apps/dev": { @@ -181,6 +211,8 @@ "@alinea/dashboard": ["@alinea/dashboard@workspace:src/dashboard"], + "@alinea/demo": ["@alinea/demo@workspace:apps/demo"], + "@alinea/dev": ["@alinea/dev@workspace:apps/dev"], "@alinea/field.richtext": ["@alinea/field.richtext@workspace:src/field/richtext"], @@ -193,6 +225,8 @@ "@alinea/ui": ["@alinea/ui@workspace:src/ui"], + "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], + "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], "@babel/code-frame": ["@babel/code-frame@7.24.7", "", { "dependencies": { "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" } }, "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA=="], @@ -265,6 +299,8 @@ "@bundled-es-modules/tough-cookie": ["@bundled-es-modules/tough-cookie@0.1.6", "", { "dependencies": { "@types/tough-cookie": "^4.0.5", "tough-cookie": "^4.1.4" } }, "sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw=="], + "@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="], + "@dependents/detective-less": ["@dependents/detective-less@3.0.2", "", { "dependencies": { "gonzales-pe": "^4.3.0", "node-source-walk": "^5.0.1" } }, "sha512-1YUvQ+e0eeTWAHoN8Uz2x2U37jZs6IGutiIE5LXId7cxfUGhtZjzxE06FdUiuiRrW+UE0vNCdSNPH2lY4dQCOQ=="], "@dnd-kit/accessibility": ["@dnd-kit/accessibility@3.1.0", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-ea7IkhKvlJUv9iSHJOnxinBcoOI3ppGnnL+VDJ75O45Nss6HtZd8IdN8touXPDtASfeI2T2LImb8VOZcL47wjQ=="], @@ -349,6 +385,14 @@ "@floating-ui/utils": ["@floating-ui/utils@0.2.9", "", {}, "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="], + "@fortawesome/fontawesome-common-types": ["@fortawesome/fontawesome-common-types@7.1.0", "", {}, "sha512-l/BQM7fYntsCI//du+6sEnHOP6a74UixFyOYUyz2DLMXKx+6DEhfR3F2NYGE45XH1JJuIamacb4IZs9S0ZOWLA=="], + + "@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@7.1.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "7.1.0" } }, "sha512-fNxRUk1KhjSbnbuBxlWSnBLKLBNun52ZBTcs22H/xEEzM6Ap81ZFTQ4bZBxVQGQgVY0xugKGoRcCbaKjLQ3XZA=="], + + "@fortawesome/free-solid-svg-icons": ["@fortawesome/free-solid-svg-icons@7.1.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "7.1.0" } }, "sha512-Udu3K7SzAo9N013qt7qmm22/wo2hADdheXtBfxFTecp+ogsc0caQNRKEb7pkvvagUGOpG9wJC1ViH6WXs8oXIA=="], + + "@fortawesome/react-fontawesome": ["@fortawesome/react-fontawesome@3.1.0", "", { "peerDependencies": { "@fortawesome/fontawesome-svg-core": "~6 || ~7", "react": "^18.0.0 || ^19.0.0" } }, "sha512-5OUQH9aDH/xHJwnpD4J7oEdGvFGJgYnGe0UebaPIdMW9UxYC/f5jv2VjVEgnikdJN0HL8yQxp9Nq+7gqGZpIIA=="], + "@headless-tree/core": ["@headless-tree/core@0.0.15", "", {}, "sha512-tWzBP/NqIqYo5xgRf/WpspOvSx4geHuPje+QtawplhoCb8Jtu0u01bE8d+8vMSlcOQrWW2f74igMYPqWPu+UEQ=="], "@headless-tree/react": ["@headless-tree/react@0.0.15", "", { "peerDependencies": { "@headless-tree/core": "*", "react": "*", "react-dom": "*" } }, "sha512-iZP7mDbOEdc5QYDzZ6r4/6jxmV9DajS48bXwaTy088/mNebJcwuDAwE1/pGYAhvo4VcnG/LR4maAZYWaZlrPGw=="], @@ -409,6 +453,8 @@ "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.5", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg=="], + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], "@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="], @@ -493,6 +539,14 @@ "@remirror/core-constants": ["@remirror/core-constants@3.0.0", "", {}, "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg=="], + "@rjsf/core": ["@rjsf/core@6.1.2", "", { "dependencies": { "lodash": "^4.17.21", "lodash-es": "^4.17.21", "markdown-to-jsx": "^8.0.0", "prop-types": "^15.8.1" }, "peerDependencies": { "@rjsf/utils": "^6.x", "react": ">=18" } }, "sha512-fcEO6kArMcVIzTBoBxNStqxzAL417NDw049nmNx11pIcMwUnU5sAkSW18c8kgZOT6v1xaZhQrY+X5cBzzHy9+g=="], + + "@rjsf/daisyui": ["@rjsf/daisyui@6.1.2", "", { "dependencies": { "@fortawesome/fontawesome-svg-core": "^7.1.0", "@fortawesome/free-solid-svg-icons": "^7.1.0", "@fortawesome/react-fontawesome": "^3.1.0", "date-fns": "^4.1.0", "react-day-picker": "^9.11.1", "tailwindcss": "^4.1.12" }, "peerDependencies": { "@rjsf/core": "^6.x", "@rjsf/utils": "^6.x", "daisyui": "^5.0.29", "react": ">=18" } }, "sha512-vaeADgIJzNxh10WDzUkMUK4RO0G3HkRzHXetIswb6u4/2sLZ/0KWH/jV6++3FBdnUbkmDDR5CekSQPpJYl4L0Q=="], + + "@rjsf/utils": ["@rjsf/utils@6.1.2", "", { "dependencies": { "@x0k/json-schema-merge": "^1.0.2", "fast-uri": "^3.1.0", "jsonpointer": "^5.0.1", "lodash": "^4.17.21", "lodash-es": "^4.17.21", "react-is": "^18.3.1" }, "peerDependencies": { "react": ">=18" } }, "sha512-Px3FIkE1KK0745Qng9v88RZ0O7hcLf/1JUu0j00g+r6C8Zyokna42Hz/5TKyyQSKJqgVYcj2Z47YroVLenUM3A=="], + + "@rjsf/validator-ajv8": ["@rjsf/validator-ajv8@6.1.2", "", { "dependencies": { "ajv": "^8.17.1", "ajv-formats": "^2.1.1", "lodash": "^4.17.21", "lodash-es": "^4.17.21" }, "peerDependencies": { "@rjsf/utils": "^6.x" } }, "sha512-9P3np2d+TaZcFTEFLocbj19fqrAWB/bxtY0Y8EjP8Oiz8LL+/wUITaN3Wx9uxzWerJyphfpZXWhUS9XkllDLig=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.21.1", "", { "os": "android", "cpu": "arm" }, "sha512-2thheikVEuU7ZxFXubPDOtspKn1x0yqaYQwvALVtEcvFhMifPADBrgRPyHV0TF3b+9BgvgjgagVyvA/UqPZHmg=="], "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.21.1", "", { "os": "android", "cpu": "arm64" }, "sha512-t1lLYn4V9WgnIFHXy1d2Di/7gyzBWS8G5pQSXdZqfrdCGTwi1VasRMSS81DTYb+avDs/Zz4A6dzERki5oRYz1g=="], @@ -555,6 +609,36 @@ "@swc/types": ["@swc/types@0.1.12", "", { "dependencies": { "@swc/counter": "^0.1.3" } }, "sha512-wBJA+SdtkbFhHjTMYH+dEH1y4VpfGdAc2Kw/LK09i9bXd/K6j6PkDcFCEzb6iVfZMkPRrl/q0e3toqTAJdkIVA=="], + "@tailwindcss/node": ["@tailwindcss/node@4.1.16", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.19", "source-map-js": "^1.2.1", "tailwindcss": "4.1.16" } }, "sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.16", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.16", "@tailwindcss/oxide-darwin-arm64": "4.1.16", "@tailwindcss/oxide-darwin-x64": "4.1.16", "@tailwindcss/oxide-freebsd-x64": "4.1.16", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.16", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.16", "@tailwindcss/oxide-linux-arm64-musl": "4.1.16", "@tailwindcss/oxide-linux-x64-gnu": "4.1.16", "@tailwindcss/oxide-linux-x64-musl": "4.1.16", "@tailwindcss/oxide-wasm32-wasi": "4.1.16", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.16", "@tailwindcss/oxide-win32-x64-msvc": "4.1.16" } }, "sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.16", "", { "os": "android", "cpu": "arm64" }, "sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.16", "", { "os": "darwin", "cpu": "arm64" }, "sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.16", "", { "os": "darwin", "cpu": "x64" }, "sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.16", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16", "", { "os": "linux", "cpu": "arm" }, "sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.16", "", { "os": "linux", "cpu": "x64" }, "sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.16", "", { "os": "linux", "cpu": "x64" }, "sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.16", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.0.7", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.16", "", { "os": "win32", "cpu": "arm64" }, "sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.16", "", { "os": "win32", "cpu": "x64" }, "sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg=="], + + "@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.16", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.16", "@tailwindcss/oxide": "4.1.16", "postcss": "^8.4.41", "tailwindcss": "4.1.16" } }, "sha512-Qn3SFGPXYQMKR/UtqS+dqvPrzEeBZHrFA92maT4zijCVggdsXnDBMsPFJo1eArX3J+O+Gi+8pV4PkqjLCNBk3A=="], + "@tanstack/react-virtual": ["@tanstack/react-virtual@3.10.6", "", { "dependencies": { "@tanstack/virtual-core": "3.10.6" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "sha512-xaSy6uUxB92O8mngHZ6CvbhGuqxQ5lIZWCBy+FjhrbHmOwc6BnOnKkYm2FsB1/BpKw/+FVctlMbEtI+F6I1aJg=="], "@tanstack/virtual-core": ["@tanstack/virtual-core@3.10.6", "", {}, "sha512-1giLc4dzgEKLMx5pgKjL6HlG5fjZMgCjzlKAlpr7yoUtetVPELgER1NtephAI910nMwfPTHNyWKSFmJdHkz2Cw=="], @@ -625,6 +709,8 @@ "@types/babel__traverse": ["@types/babel__traverse@7.20.6", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg=="], + "@types/better-sqlite3": ["@types/better-sqlite3@7.6.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA=="], + "@types/bun": ["@types/bun@1.1.17", "", { "dependencies": { "bun-types": "1.1.44" } }, "sha512-zZt0Kao/8hAwNOXh4bmt8nKbMEd4QD8n7PeTGF+NZTVY5ouXhU/TX7jUj4He1p7mgY+WdplnU1B6MB1j17vdzg=="], "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], @@ -647,6 +733,8 @@ "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + "@types/json5": ["@types/json5@0.0.30", "", {}, "sha512-sqm9g7mHlPY/43fcSNrCYfOeX9zkTTK+euO5E6+CVijSMm5tTjkVdwdqRkY3ljjIAf8679vps5jKUoJBCLsMDA=="], "@types/jsonfile": ["@types/jsonfile@6.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ=="], @@ -717,6 +805,8 @@ "@vitejs/plugin-react-swc": ["@vitejs/plugin-react-swc@3.7.0", "", { "dependencies": { "@swc/core": "^1.5.7" }, "peerDependencies": { "vite": "^4 || ^5" } }, "sha512-yrknSb3Dci6svCd/qhHqhFPDSw0QtjumcqdKMoNNzmOl5lMXTTiqzjWtG4Qask2HdvvzaNgSunbQGet8/GrKdA=="], + "@x0k/json-schema-merge": ["@x0k/json-schema-merge@1.0.2", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-1734qiJHNX3+cJGDMMw2yz7R+7kpbAtl5NdPs1c/0gO5kYT6s4dMbLXiIfpZNsOYhGZI3aH7FWrj4Zxz7epXNg=="], + "@zkochan/rimraf": ["@zkochan/rimraf@3.0.2", "", {}, "sha512-GBf4ua7ogWTr7fATnzk/JLowZDBnBJMm8RkMaC/KcvxZ9gxbMWix0/jImd815LmqKyIHZ7h7lADRddGMdGBuCA=="], "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], @@ -725,6 +815,10 @@ "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + + "ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], + "ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="], "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="], @@ -777,10 +871,14 @@ "better-path-resolve": ["better-path-resolve@1.0.0", "", { "dependencies": { "is-windows": "^1.0.0" } }, "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g=="], + "better-sqlite3": ["better-sqlite3@12.4.1", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-3yVdyZhklTiNrtg+4WqHpJpFDd+WHTg2oM7UcR80GqL05AOV0xEJzc6qNvFYoEtE+hRp1n9MpN6/+4yhlGkDXQ=="], + "big-integer": ["big-integer@1.6.52", "", {}, "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg=="], "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], + "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], @@ -833,6 +931,8 @@ "cito": ["cito@0.3.3", "", {}, "sha512-0m6c54DYkU8Q4IcEnnO098EIH0eteLrAyCPQHEcBE4Rro97XA0RzAkJlj+CSBKbKveb+EDuoauO/P9Jk6QoPmw=="], + "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], + "classnames": ["classnames@2.5.1", "", {}, "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="], "cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="], @@ -903,6 +1003,8 @@ "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + "daisyui": ["daisyui@5.3.8", "", {}, "sha512-ihDXb07IzM/2ugkwBWdy2LFCaepdn1oGsKIsR3gNG/VuTAmS60+HUG9rskjR5BzyJOVVUDDpWoiX3PBDIT3DYQ=="], + "data-view-buffer": ["data-view-buffer@1.0.1", "", { "dependencies": { "call-bind": "^1.0.6", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA=="], "data-view-byte-length": ["data-view-byte-length@1.0.1", "", { "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ=="], @@ -911,6 +1013,10 @@ "dataloader": ["dataloader@2.2.2", "", {}, "sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g=="], + "date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="], + + "date-fns-jalali": ["date-fns-jalali@4.1.0-0", "", {}, "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg=="], + "debug": ["debug@4.3.6", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg=="], "decode-named-character-reference": ["decode-named-character-reference@1.0.2", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg=="], @@ -997,7 +1103,7 @@ "end-of-stream": ["end-of-stream@1.4.4", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q=="], - "enhanced-resolve": ["enhanced-resolve@5.17.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg=="], + "enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], @@ -1063,8 +1169,12 @@ "fast-glob": ["fast-glob@3.3.2", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow=="], + "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + "fastq": ["fastq@1.17.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w=="], + "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], + "filing-cabinet": ["filing-cabinet@3.3.1", "", { "dependencies": { "app-module-path": "^2.2.0", "commander": "^2.20.3", "debug": "^4.3.3", "enhanced-resolve": "^5.8.3", "is-relative-path": "^1.0.2", "module-definition": "^3.3.1", "module-lookup-amd": "^7.0.1", "resolve": "^1.21.0", "resolve-dependency-path": "^2.0.0", "sass-lookup": "^3.0.0", "stylus-lookup": "^3.0.1", "tsconfig-paths": "^3.10.1", "typescript": "^3.9.7" }, "bin": { "filing-cabinet": "bin/cli.js" } }, "sha512-renEK4Hh6DUl9Vl22Y3cxBq1yh8oNvbAdXnhih0wVpmea+uyKjC9K4QeRjUaybIiIewdzfum+Fg15ZqJ/GyCaA=="], "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], @@ -1299,6 +1409,8 @@ "isomorphic.js": ["isomorphic.js@0.2.5", "", {}, "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw=="], + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + "jotai": ["jotai@2.9.3", "", { "peerDependencies": { "@types/react": ">=17.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@types/react", "react"] }, "sha512-IqMWKoXuEzWSShjd9UhalNsRGbdju5G2FrqNLQJT+Ih6p41VNYe2sav5hnwQx4HJr25jq9wRqvGSWGviGG6Gjw=="], "js-sha3": ["js-sha3@0.8.0", "", {}, "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="], @@ -1309,12 +1421,16 @@ "json-parse-better-errors": ["json-parse-better-errors@1.0.2", "", {}, "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="], + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], "jsonfile": ["jsonfile@6.1.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ=="], "jsonparse": ["jsonparse@1.3.1", "", {}, "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="], + "jsonpointer": ["jsonpointer@5.0.1", "", {}, "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ=="], + "jsonstream-next": ["jsonstream-next@3.0.0", "", { "dependencies": { "jsonparse": "^1.2.0", "through2": "^4.0.2" }, "bin": { "jsonstream-next": "bin.js" } }, "sha512-aAi6oPhdt7BKyQn1SrIIGZBt0ukKuOUE1qV6kJ3GgioSOYzsRc8z9Hfr1BVmacA/jLe9nARfmgMGgn68BqIAgg=="], "keygrip": ["keygrip@1.1.0", "", { "dependencies": { "tsscmp": "1.0.6" } }, "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ=="], @@ -1331,6 +1447,30 @@ "lib0": ["lib0@0.2.88", "", { "dependencies": { "isomorphic.js": "^0.2.4" }, "bin": { "0serve": "bin/0serve.js", "0gentesthtml": "bin/gentesthtml.js" } }, "sha512-KyroiEvCeZcZEMx5Ys+b4u4eEBbA1ch7XUaBhYpwa/nPMrzTjUhI4RfcytmQfYoTBPcdyx+FX6WFNIoNuJzJfQ=="], + "lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], + "lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="], "linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="], @@ -1339,6 +1479,10 @@ "loader-utils": ["loader-utils@3.3.1", "", {}, "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg=="], + "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], + + "lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], + "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], @@ -1351,8 +1495,12 @@ "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + "lucide-react": ["lucide-react@0.546.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Z94u6fKT43lKeYHiVyvyR8fT7pwCzDu7RyMPpTvh054+xahSgj4HFQ+NmflvzdXsoAjYGdCguGaFKYuvq0ThCQ=="], + "madge": ["madge@6.1.0", "", { "dependencies": { "chalk": "^4.1.1", "commander": "^7.2.0", "commondir": "^1.0.1", "debug": "^4.3.1", "dependency-tree": "^9.0.0", "detective-amd": "^4.0.1", "detective-cjs": "^4.0.0", "detective-es6": "^3.0.0", "detective-less": "^1.0.2", "detective-postcss": "^6.1.0", "detective-sass": "^4.0.1", "detective-scss": "^3.0.0", "detective-stylus": "^2.0.1", "detective-typescript": "^9.0.0", "ora": "^5.4.1", "pluralize": "^8.0.0", "precinct": "^8.1.0", "pretty-ms": "^7.0.1", "rc": "^1.2.7", "stream-to-array": "^2.3.0", "ts-graphviz": "^1.5.0", "walkdir": "^0.4.1" }, "peerDependencies": { "typescript": "^3.9.5 || ^4.9.5 || ^5" }, "optionalPeers": ["typescript"], "bin": { "madge": "bin/cli.js" } }, "sha512-irWhT5RpFOc6lkzGHKLihonCVgM0YtfNUh4IrFeW3EqHpnt/JHUG3z26j8PeJEktCGB4tmGOOOJi1Rl/ACWucQ=="], + "magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="], + "make-dir": ["make-dir@2.1.0", "", { "dependencies": { "pify": "^4.0.1", "semver": "^5.6.0" } }, "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA=="], "markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="], @@ -1361,6 +1509,8 @@ "markdown-table": ["markdown-table@3.0.3", "", {}, "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw=="], + "markdown-to-jsx": ["markdown-to-jsx@8.0.0", "", { "peerDependencies": { "react": ">= 0.14.0" }, "optionalPeers": ["react"] }, "sha512-hWEaRxeCDjes1CVUQqU+Ov0mCqBqkGhLKjL98KdbwHSgEWZZSJQeGlJQatVfeZ3RaxrfTrZZ3eczl2dhp5c/pA=="], + "match-sorter": ["match-sorter@6.3.4", "", { "dependencies": { "@babel/runtime": "^7.23.8", "remove-accents": "0.5.0" } }, "sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg=="], "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA=="], @@ -1709,15 +1859,17 @@ "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], - "react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="], - "react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="], + "react-day-picker": ["react-day-picker@9.11.1", "", { "dependencies": { "@date-fns/tz": "^1.4.1", "date-fns": "^4.1.0", "date-fns-jalali": "^4.1.0-0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-l3ub6o8NlchqIjPKrRFUCkTUEq6KwemQlfv3XZzzwpUeGwmDJ+0u0Upmt38hJyd7D/vn2dQoOoLV/qAp0o3uUw=="], + + "react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="], "react-hotkeys-hook": ["react-hotkeys-hook@4.5.0", "", { "peerDependencies": { "react": ">=16.8.1", "react-dom": ">=16.8.1" } }, "sha512-Samb85GSgAWFQNvVt3PS90LPPGSf9mkH/r4au81ZP1yOIFayLC3QAvqTgGtJ8YEDMXtPmaVBs6NgipHO6h4Mug=="], "react-inspector": ["react-inspector@6.0.2", "", { "peerDependencies": { "react": "^16.8.4 || ^17.0.0 || ^18.0.0" } }, "sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ=="], - "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], + "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], "react-query": ["react-query@3.39.3", "", { "dependencies": { "@babel/runtime": "^7.5.5", "broadcast-channel": "^3.4.1", "match-sorter": "^6.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g=="], @@ -1757,6 +1909,8 @@ "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + "requirejs": ["requirejs@2.3.7", "", { "bin": { "r.js": "bin/r.js", "r_js": "bin/r.js" } }, "sha512-DouTG8T1WanGok6Qjg2SXuCMzszOo0eHeH9hDZ5Y4x8Je+9JB38HdTLT4/VA8OaUhBa0JPVHJ0pyBkM1z+pDsw=="], "requirejs-config-file": ["requirejs-config-file@4.0.0", "", { "dependencies": { "esprima": "^4.0.0", "stringify-object": "^3.2.1" } }, "sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw=="], @@ -1845,7 +1999,7 @@ "sax": ["sax@1.3.0", "", {}, "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA=="], - "scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], "semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], @@ -1955,6 +2109,10 @@ "tabbable": ["tabbable@6.2.0", "", {}, "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="], + "tailwind-merge": ["tailwind-merge@3.3.1", "", {}, "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g=="], + + "tailwindcss": ["tailwindcss@4.1.16", "", {}, "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA=="], + "tapable": ["tapable@2.2.1", "", {}, "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="], "tar-fs": ["tar-fs@3.0.6", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^2.1.1", "bare-path": "^2.1.0" } }, "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w=="], @@ -2001,6 +2159,8 @@ "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], + "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], + "type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], @@ -2119,6 +2279,12 @@ "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + "@alinea/demo/@types/node": ["@types/node@20.12.14", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg=="], + + "@alinea/demo/@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="], + + "@alinea/demo/@types/react-dom": ["@types/react-dom@19.2.2", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw=="], + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -2141,6 +2307,22 @@ "@swc/helpers/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@tailwindcss/node/source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.6.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-zq/ay+9fNIJJtJiZxdTnXS20PllcYMX3OE23ESc4HK/bdYu3cOWYVhsOhVnXALfU/uqJIxn5NBPd9z4v+SfoSg=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.6.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@tailwindcss/postcss/postcss": ["postcss@8.4.41", "", { "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.1", "source-map-js": "^1.2.0" } }, "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ=="], + "@types/fs-extra/@types/node": ["@types/node@20.0.0", "", {}, "sha512-cD2uPTDnQQCVpmRefonO98/PPijuOnnEy5oytWJFPY1N9aJCz2wJ5kSGWO+zJoed2cY2JxQh6yBuUq4vIn61hw=="], "@types/glob/@types/node": ["@types/node@20.0.0", "", {}, "sha512-cD2uPTDnQQCVpmRefonO98/PPijuOnnEy5oytWJFPY1N9aJCz2wJ5kSGWO+zJoed2cY2JxQh6yBuUq4vIn61hw=="], @@ -2203,6 +2385,8 @@ "filing-cabinet/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + "filing-cabinet/enhanced-resolve": ["enhanced-resolve@5.17.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg=="], + "filing-cabinet/tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="], "filing-cabinet/typescript": ["typescript@3.9.10", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q=="], @@ -2215,12 +2399,16 @@ "less/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "lightningcss/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "load-json-file/strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], "madge/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], "madge/pretty-ms": ["pretty-ms@7.0.1", "", { "dependencies": { "parse-ms": "^2.1.0" } }, "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q=="], + "magic-string/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + "make-dir/pify": ["pify@4.0.1", "", {}, "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="], "make-dir/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], @@ -2271,6 +2459,8 @@ "precinct/node-source-walk": ["node-source-walk@4.3.0", "", { "dependencies": { "@babel/parser": "^7.0.0" } }, "sha512-8Q1hXew6ETzqKRAs3jjLioSxNfT1cx74ooiF8RlAONwVMcfq+UdzLC2eB5qcPldUxaE5w3ytLkrmV1TGddhZTA=="], + "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], + "prosemirror-trailing-node/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "read-pkg/path-type": ["path-type@3.0.0", "", { "dependencies": { "pify": "^3.0.0" } }, "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg=="], @@ -2315,6 +2505,8 @@ "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "@alinea/demo/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + "@babel/highlight/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="], "@esbx/reporter/pretty-ms/parse-ms": ["parse-ms@2.1.0", "", {}, "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA=="], diff --git a/dev.js b/dev.js index 733ae823e..61744c33f 100644 --- a/dev.js +++ b/dev.js @@ -28,7 +28,7 @@ async function run({production, dir, config}) { sade('dev', true) .option('--production', 'Run in production mode') - .option('--dir', 'Development directory', 'apps/web') + .option('--dir', 'Development directory') .option('--config', 'Config file') .action(opts => run(opts)) .parse(process.argv) diff --git a/package.json b/package.json index 13b14368a..121de7619 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "type": "module", "scripts": { "dev": "run-p dev:run", - "web": "run-p dev:check web:run", + "demo": "run-p dev:check demo:run", "tag": "bun tag.js", "bump": "bun bump.js", "stories": "ladle serve", @@ -21,7 +21,8 @@ "type": "tsc --diagnostics", "dev:check": "tsc -w", "dev:run": "bun build.ts --watch -- node dev.js --dir apps/dev", - "web:run": "bun build.ts --watch -- node dev.js -- bun run --cwd apps/web dev", + "demo:build": "cd apps/demo && bun run build", + "demo:run": "bun build.ts --watch -- node dev.js --dir apps/demo -- bun run --cwd apps/demo dev", "trace": "tsc --diagnostics --generateTrace private/trace && analyze-trace private/trace & speedscope private/trace/trace.json", "prepublishOnly": "bun run build" }, @@ -38,6 +39,7 @@ ], "workspaces": [ "apps/dev", + "apps/demo", "src/adapter", "src/backend", "src/cli", @@ -51,8 +53,8 @@ "esbuild": "^0.25.4" }, "peerDependencies": { - "react": "*", - "react-dom": "*" + "react": "^19.2.0", + "react-dom": "^19.2.0" }, "devDependencies": { "@alinea/styler": "^1.0.3", @@ -60,6 +62,9 @@ "@biomejs/biome": "^2.1.2", "@esbx/reporter": "^0.0.20", "@esbx/workspaces": "^0.0.20", + "@rjsf/core": "6.1.2", + "@rjsf/utils": "6.1.2", + "@rjsf/validator-ajv8": "6.1.2", "@ladle/react": "^4.1.2", "@types/bun": "^1.1.17", "@types/fs-extra": "^11.0.1", @@ -77,8 +82,6 @@ "npm-run-all": "^4.1.5", "postcss-modules": "^6.0.0", "postcss-pxtorem": "^6.0.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", "sass": "^1.63.4", "sass-embedded": "^1.83.4", "sharp": "0.32.6", diff --git a/src/dashboard/editor/DefaultViews.tsx b/src/dashboard/editor/DefaultViews.tsx index abb7d293c..eeb4861a7 100644 --- a/src/dashboard/editor/DefaultViews.tsx +++ b/src/dashboard/editor/DefaultViews.tsx @@ -1,6 +1,7 @@ import {CheckInput} from 'alinea/field/check/CheckField.view' import {CodeInput} from 'alinea/field/code/CodeField.view' import {DateInput} from 'alinea/field/date/DateField.view' +import {FormInput} from 'alinea/field/form/FormField.view' import {HiddenInput} from 'alinea/field/hidden/HiddenField.view' import {JsonInput} from 'alinea/field/json/JsonField.view' import { @@ -43,5 +44,6 @@ export const defaultViews = { [viewKeys.MediaExplorer]: MediaExplorer, [viewKeys.MediaFile]: FileEntry, [viewKeys.FileSummaryRow]: FileSummaryRow, - [viewKeys.FileSummaryThumb]: FileSummaryThumb + [viewKeys.FileSummaryThumb]: FileSummaryThumb, + [viewKeys.FormInput]: FormInput } diff --git a/src/dashboard/editor/ViewKeys.ts b/src/dashboard/editor/ViewKeys.ts index f32fadc35..5d190a070 100644 --- a/src/dashboard/editor/ViewKeys.ts +++ b/src/dashboard/editor/ViewKeys.ts @@ -19,5 +19,6 @@ export const viewKeys = { MediaExplorer: 'alinea/dashboard/view/MediaExplorer#MediaExplorer', MediaFile: 'alinea/dashboard/view/media/FileEntry#FileEntry', FileSummaryRow: 'alinea/dashboard/view/media/FileSummary#FileSummaryRow', - FileSummaryThumb: 'alinea/dashboard/view/media/FileSummary#FileSummaryThumb' + FileSummaryThumb: 'alinea/dashboard/view/media/FileSummary#FileSummaryThumb', + FormInput: 'alinea/field/form/FormField.view#FormInput' } diff --git a/src/field.ts b/src/field.ts index c85bb8995..030b4e79f 100644 --- a/src/field.ts +++ b/src/field.ts @@ -2,6 +2,7 @@ export * from 'alinea/core/field/CreateField' export {check} from 'alinea/field/check' export {code} from 'alinea/field/code' export {date} from 'alinea/field/date' +export {form} from 'alinea/field/form' export {json} from 'alinea/field/json' export {entry} from 'alinea/field/link/EntryLink' export {file} from 'alinea/field/link/FileLink' diff --git a/src/field/form.ts b/src/field/form.ts new file mode 100644 index 000000000..19be5b740 --- /dev/null +++ b/src/field/form.ts @@ -0,0 +1 @@ +export * from 'alinea/field/form/FormField' diff --git a/src/field/form/FormField.module.scss b/src/field/form/FormField.module.scss new file mode 100644 index 000000000..8d6ed6c7b --- /dev/null +++ b/src/field/form/FormField.module.scss @@ -0,0 +1,21 @@ +.root { + &-input { + display: block; + background: transparent; + border: none; + color: inherit; + font: inherit; + width: 100%; + resize: none; + padding: 9px 14px; + border-radius: var(--alinea-border-radius); + line-height: 1.5; + background: var(--alinea-fields); + box-shadow: var(--alinea-fields-shadow); + + &:focus { + background: var(--alinea-fields-selected); + outline: 2px solid var(--alinea-fields-focus); + } + } +} diff --git a/src/field/form/FormField.stories.tsx b/src/field/form/FormField.stories.tsx new file mode 100644 index 000000000..7e41eb70c --- /dev/null +++ b/src/field/form/FormField.stories.tsx @@ -0,0 +1,31 @@ +import {type} from 'alinea/core/Type' +import {useForm} from 'alinea/dashboard/atoms/FormAtoms' +import {InputForm} from 'alinea/dashboard/editor/InputForm' +import {VStack} from 'alinea/ui' +import {UIStory} from 'alinea/ui/UIStory' +import {form} from './FormField.js' + +const fields = type('Field', { + fields: { + date: form('Form', {}), + focused: form('Form', {}), + readOnly: form('Form (read-only)', { + readOnly: true + }) + } +}) + +export function DateField() { + const form = useForm(fields) + return ( + + + + + + ) +} + +export default { + title: 'Fields / Date' +} diff --git a/src/field/form/FormField.ts b/src/field/form/FormField.ts new file mode 100644 index 000000000..9e241f122 --- /dev/null +++ b/src/field/form/FormField.ts @@ -0,0 +1,91 @@ +import type {FieldOptions, WithoutLabel} from 'alinea/core' +import {ScalarField} from 'alinea/core/field/ScalarField' +import type {Infer} from 'alinea/core/Infer.js' +import type {Schema} from 'alinea/core/Schema.js' +import type {Type} from 'alinea/core/Type.js' +import {viewKeys} from 'alinea/dashboard/editor/ViewKeys' +import type {JSONSchema7} from 'json-schema' +import type {ReactNode} from 'react' +import {FormFileField} from './base/FormFileField.js' +import {FormSelectField} from './base/FormSelectField.js' +import {FormTextField} from './base/FormTextField.js' +import {FormArrayField} from './composed/FormArrayField.js' +import type {RjsfHandler} from './RjsfHandler.js' + +export type FormDefinition = { + schema: JSONSchema7 + ui: any +} + +/** Optional settings to configure a text field */ +export interface FormOptions extends FieldOptions { + /** Width of the field in the dashboard UI (0-1) */ + width?: number + /** Add instructional text to a field */ + help?: ReactNode + /** Display a minimal version */ + initialValue?: FormDefinition + /** Simple form fields like text input, checkboxes, selects, etc */ + baseFields?: Record> + /** Composed fields like arrays, objects, etc */ + composedFields?: Record> +} + +export type FormFieldDefinition = { + schema: Type + rjsfToField: ( + key: string, + schema: FormDefinition['schema'], + uiSchema: FormDefinition['ui'], + handler: RjsfHandler + ) => Infer | undefined + addFieldToRjsf: ( + properties: Record, + uiSchema: Record, + row: Infer.ListItem, + handler: RjsfHandler + ) => void +} + +export type ComposedFormFieldDefinition = { + generateSchema: (baseFields: Schema) => Type + rjsfToField: ( + key: string, + fieldSchema: JSONSchema7, + schema: FormDefinition['schema'], + uiSchema: FormDefinition['ui'] + ) => Infer | undefined + addFieldToRjsf: ( + properties: Record, + uiSchema: Record, + row: Infer.ListItem, + handler: RjsfHandler + ) => void +} + +/** Internal representation of a date field */ +export class FormField extends ScalarField {} + +/** Create a date field configuration */ +export function form( + label: string, + options: WithoutLabel = {} +): FormField { + const defaultBaseFields = { + FormFileField, + FormSelectField, + FormTextField + } as unknown as Record> // TODO: fix typing + const composedBaseFields = { + FormArrayField + } as unknown as Record> // TODO: fix typing + return new FormField({ + options: { + label, + ...options, + baseFields: options.baseFields || defaultBaseFields, + composedFields: options.composedFields || composedBaseFields + }, + view: viewKeys.FormInput + }) +} diff --git a/src/field/form/FormField.view.tsx b/src/field/form/FormField.view.tsx new file mode 100644 index 000000000..16c80c15d --- /dev/null +++ b/src/field/form/FormField.view.tsx @@ -0,0 +1,235 @@ +import styler from '@alinea/styler' +import Form from '@rjsf/core' +import validator from '@rjsf/validator-ajv8' +import {type} from 'alinea/core/Type' +import {useForm} from 'alinea/dashboard/atoms/FormAtoms' +import {InputForm} from 'alinea/dashboard/editor/InputForm' +import {useField} from 'alinea/dashboard/editor/UseField' +import {HStack, VStack} from 'alinea/ui/Stack' +import {TextareaAutosize} from 'alinea/ui/util/TextareaAutosize' +import {useAtomValue} from 'jotai' +import {useEffect, useMemo, useState} from 'react' +import {list} from '../list.js' +// import {VisualBuilder} from './VisualBuilder.js' +import type {FormDefinition, FormField, FormOptions} from './FormField' +import css from './FormField.module.scss' +import {RjsfHandler} from './RjsfHandler.js' + +const styles = styler(css) + +export interface FormInputProps { + field: FormField +} + +const tabs = ['builder', 'schema', 'preview'] as const +type Tab = (typeof tabs)[number] + +export function FormInput({field}: FormInputProps) { + const {options, value, mutator} = useField(field) + + const [tab, setTab] = useState('builder') + const formSchema = value?.schema || {} + const uiSchema = value?.ui || {} + + return ( +
+
+ {tabs.map(t => ( + + ))} +
+
+ {tab === 'builder' && ( + mutator(value)} + /> + )} + {tab === 'schema' && ( + mutator({schema, ui: uiSchema})} + setUiSchema={ui => mutator({schema: formSchema, ui})} + /> + )} + {tab === 'preview' && ( + + )} +
+
+ ) +} + +function Preview({ + formSchema, + uiSchema +}: { + formSchema: FormDefinition['schema'] + uiSchema: FormDefinition['ui'] +}) { + const log = (type: string) => console.log.bind(console, type) + return ( +
+ ) +} + +function FormSchema({ + formSchema, + uiSchema, + setFormSchema, + setUiSchema +}: { + formSchema: FormDefinition['schema'] + uiSchema: FormDefinition['ui'] + setFormSchema: (schema: FormDefinition['schema']) => void + setUiSchema: (ui: FormDefinition['ui']) => void +}) { + const [text, setText] = useState(JSON.stringify(formSchema, null, 2)) + const [valid, setValid] = useState(true) + + return ( +
+

Schema definition

+
+ + +
+
+ + +
+
+ ) +} + +function JSONTextAreaField({ + value, + mutator +}: { + value: any + mutator: (value: any) => void +}) { + const [text, setText] = useState(JSON.stringify(value, null, 2)) + const [valid, setValid] = useState(true) + return ( + + { + setText(e.currentTarget.value) + try { + const parsed = JSON.parse(e.currentTarget.value) + mutator(parsed) + setValid(true) + } catch { + setValid(false) + } + }} + onKeyDown={e => { + if (e.key === 'Tab') { + const target = e.target as HTMLInputElement + const start = target.selectionStart! + const end = target.selectionEnd! + const value = target.value + + if (end !== value.length) { + e.preventDefault() + target.value = `${value.substring(0, start)} ${value.substring(end)}` + target.selectionStart = target.selectionEnd = start + 2 + } + } + }} + onBlur={e => { + if (valid) { + setText(JSON.stringify(value, null, 2)) + } + }} + placeholder={'{}'} + /> + + ) +} + +export function VisualBuilder({ + options, + formSchema, + uiSchema, + setSchemas +}: { + options: FormOptions + formSchema: FormDefinition['schema'] + uiSchema: FormDefinition['ui'] + setSchemas: (value: FormDefinition) => void +}) { + const fields = useMemo(() => { + const baseSchema: any = {} + for (const entry of Object.entries(options.baseFields || {})) { + const [key, fieldDef] = entry + baseSchema[key] = fieldDef.schema + } + const composedSchema: any = {} + for (const entry of Object.entries(options.composedFields || {})) { + const [key, fieldDef] = entry + composedSchema[key] = fieldDef.generateSchema(baseSchema) + } + return type('Fields', { + fields: { + list: list('List', { + schema: { + ...baseSchema, + ...composedSchema + } + }) + } + }) + }, []) + + const listInitialValue = useMemo(() => { + const handler = new RjsfHandler(formSchema, uiSchema, options) + return handler.generateFields() + }, []) + + const form = useForm(fields, { + initialValue: {list: listInitialValue} + }) + const listField = form.fieldInfo(fields.list) + const data = useAtomValue(listField.value) + + useEffect(() => { + const handler = new RjsfHandler(formSchema, uiSchema, options) + const newSchemas = handler.rebuildSchemas(data) + setSchemas(newSchemas) + }, [data]) + + return ( + + + + ) +} diff --git a/src/field/form/RjsfHandler.ts b/src/field/form/RjsfHandler.ts new file mode 100644 index 000000000..d37e9ad2f --- /dev/null +++ b/src/field/form/RjsfHandler.ts @@ -0,0 +1,103 @@ +import {createId} from 'alinea/core/Id' +import type {Infer} from 'alinea/core/Infer.js' +import {generateKeyBetween} from 'alinea/core/util/FractionalIndexing' +import type {JSONSchema7} from 'json-schema' +import type {FormDefinition, FormOptions} from './FormField.js' + +export class RjsfHandler { + public constructor( + public formSchema: FormDefinition['schema'], + public uiSchema: FormDefinition['ui'], + public options: FormOptions + ) {} + + public generateFields(): Infer.ListItem[] { + const fields: Infer.ListItem[] = [] + for (const key of Object.keys(this.formSchema?.properties || {})) { + const schema = this.formSchema.properties?.[key] + if (!schema || schema === true) continue + + const uiSchema = this.uiSchema?.[key] || {} + + const field = this.rjsfToField(key, schema, uiSchema) + if (field) { + const previousField = fields[fields.length - 1] + const _index = generateKeyBetween(previousField?._index || null, null) + fields.push({...field, _index}) + } + } + + return fields + } + + public rjsfToField( + key: string, + schema: FormDefinition['schema'], + uiSchema: FormDefinition['ui'] + ): Infer.ListItem | undefined { + const _id = createId() + const _index = generateKeyBetween(null, null) + + for (const entry of Object.entries(this.options.baseFields || {})) { + const [_type, fieldDef] = entry + const field = fieldDef.rjsfToField(key, schema, uiSchema, this) + if (field) return {_id, _index, _type, ...field} + } + for (const entry of Object.entries(this.options.composedFields || {})) { + const [_type, fieldDef] = entry + const field = fieldDef.rjsfToField(key, schema, uiSchema, this) + if (field) return {_id, _index, _type, ...field} + } + return undefined + } + + public rebuildSchemas(fields: Infer.ListItem[]): FormDefinition { + const properties: Record = {} + const newUiSchema: FormDefinition['ui'] = {} + + for (const field of fields) { + for (const entry of Object.entries(this.options.baseFields || {})) { + const [key, fieldDef] = entry + if (field._type === key) { + fieldDef.addFieldToRjsf(properties, newUiSchema, field, this) + break + } + } + for (const entry of Object.entries(this.options.composedFields || {})) { + const [key, fieldDef] = entry + if (field._type === key) { + fieldDef.addFieldToRjsf(properties, newUiSchema, field, this) + break + } + } + } + return { + schema: { + type: 'object', + properties + }, + ui: newUiSchema + } + } + + public addFieldToRjsf( + properties: Record, + uiSchema: Record, + field: Infer.ListItem + ) { + for (const entry of Object.entries(this.options.baseFields || {})) { + const [key, fieldDef] = entry + if (field._type === key) { + fieldDef.addFieldToRjsf(properties, uiSchema, field, this) + return + } + } + for (const entry of Object.entries(this.options.composedFields || {})) { + const [key, fieldDef] = entry + if (field._type === key) { + fieldDef.addFieldToRjsf(properties, uiSchema, field, this) + return + } + } + } +} diff --git a/src/field/form/base/FormFileField.tsx b/src/field/form/base/FormFileField.tsx new file mode 100644 index 000000000..372e9d181 --- /dev/null +++ b/src/field/form/base/FormFileField.tsx @@ -0,0 +1,58 @@ +import type {Infer} from 'alinea/core/Infer' +import {type} from 'alinea/core/Type' +import {path} from 'alinea/field/path' +import {text} from 'alinea/field/text/TextField' +import type {JSONSchema7, JSONSchema7Definition} from 'json-schema' +import type {FormDefinition, FormFieldDefinition} from '../FormField.js' + +export const Schema = type('File', { + fields: { + title: text('Label', {required: true, width: 0.5}), + key: path('Key', {required: true, width: 0.5}), + placeholder: text('Placeholder') + } +}) + +export function rjsfToField( + key: string, + schema: FormDefinition['schema'], + uiSchema: FormDefinition['ui'] +): Infer | undefined { + if (schema.type !== 'string') return undefined + if (uiSchema['ui:widget'] !== 'file' && schema.format !== 'data-url') + return undefined + + return { + title: schema.title || '[No label]', + key: key, + placeholder: uiSchema['ui:placeholder'] || '' + } +} + +export function addFieldToRjsf( + properties: Record, + uiSchema: FormDefinition['ui'], + field: Infer.ListItem +) { + const key = field.key || field._id + + const schema: JSONSchema7Definition = { + type: 'string', + title: field.title || '[No label]', + format: 'data-url' + } + properties[key] = schema + + const ui: any = {} + ui['ui:widget'] = 'file' + + if (Object.keys(ui).length > 0) { + uiSchema[key] = ui + } +} + +export const FormFileField = { + schema: Schema, + rjsfToField, + addFieldToRjsf +} satisfies FormFieldDefinition diff --git a/src/field/form/base/FormSelectField.tsx b/src/field/form/base/FormSelectField.tsx new file mode 100644 index 000000000..58c5dc80a --- /dev/null +++ b/src/field/form/base/FormSelectField.tsx @@ -0,0 +1,100 @@ +import type {RJSFSchema} from '@rjsf/utils' +import {createId} from 'alinea/core/Id' +import type {Infer} from 'alinea/core/Infer' +import {type} from 'alinea/core/Type' +import {generateKeyBetween} from 'alinea/core/util/FractionalIndexing' +import {list} from 'alinea/field/list/ListField' +import {path} from 'alinea/field/path' +import {text} from 'alinea/field/text/TextField' +import type {JSONSchema7, JSONSchema7Definition} from 'json-schema' +import {select} from '../../select.js' +import type {FormDefinition, FormFieldDefinition} from '../FormField.js' + +const Schema = type('Select', { + fields: { + title: text('Label', {required: true, width: 0.5}), + key: path('Key', {required: true, width: 0.5}), + placeholder: text('Placeholder'), + options: list('Fields', { + schema: { + Option: type('Option', { + fields: { + value: text('Value', {required: true, width: 0.5}), + label: text('Label', {required: true, width: 0.5}) + } + }) + } + }), + defaultValue: text('Default value'), + widget: select('Widget', { + options: { + radio: 'Radio Buttons', + select: 'Dropdown' + } + }) + } +}) + +function rjsfToField( + key: string, + schema: FormDefinition['schema'], + uiSchema: FormDefinition['ui'] +): Infer | undefined { + if (schema.type !== 'string') return undefined + if (!schema.oneOf) return undefined + + return { + title: schema.title || '[No label]', + key, + options: schema.oneOf.flatMap((option: any) => { + const optionId = createId() + const index = generateKeyBetween(null, null) + const value = option?.const + const label = option?.title || key + if (!key) return [] + return [ + { + _type: 'Option', + _id: optionId, + _index: index, + value, + label + } + ] + }), + placeholder: '', + defaultValue: '', + widget: uiSchema['ui:widget'] === 'select' ? 'select' : 'radio' + } +} + +function addFieldToRjsf( + properties: Record, + uiSchema: Record, + field: Infer.ListItem +) { + const key = field.key || field._id + + const schema: JSONSchema7Definition = { + type: 'string', + title: field.title || '[No label]', + oneOf: field.options.map(option => { + return { + const: option.value, + title: option.label + } + }) + } + properties[key] = schema + if (field.defaultValue) schema.default = field.defaultValue + uiSchema[key] = { + 'ui:placeholder': field.placeholder || '', + 'ui:widget': field.widget + } +} + +export const FormSelectField = { + schema: Schema, + rjsfToField, + addFieldToRjsf +} satisfies FormFieldDefinition diff --git a/src/field/form/base/FormTextField.tsx b/src/field/form/base/FormTextField.tsx new file mode 100644 index 000000000..03c5e2fb9 --- /dev/null +++ b/src/field/form/base/FormTextField.tsx @@ -0,0 +1,91 @@ +import type {Infer} from 'alinea/core/Infer' +import {type} from 'alinea/core/Type' +import {number} from 'alinea/field/number' +import {path} from 'alinea/field/path' +import {text} from 'alinea/field/text/TextField' +import type {JSONSchema7, JSONSchema7Definition} from 'json-schema' +import {select} from '../../select.js' +import type {FormDefinition, FormFieldDefinition} from '../FormField.js' +import { + autoCompleteValues, + isAutoCompleteValue +} from '../utils/autoCompleteValues.js' + +const Schema = type('Text', { + fields: { + title: text('Label', {required: true, width: 0.5}), + key: path('Key', {required: true, width: 0.5}), + widget: select('Widget', { + options: { + text: 'Text Input', + textarea: 'Text Area', + email: 'Email', + password: 'Password' + } + }), + placeholder: text('Placeholder'), + autocomplete: select('Autocomplete', { + options: autoCompleteValues + }), + defaultValue: text('Default value'), + maxLength: number('Max Length') + } +}) + +function rjsfToField( + key: string, + schema: FormDefinition['schema'], + uiSchema: FormDefinition['ui'] +): Infer | undefined { + if (schema.type !== 'string') return undefined + + let widget: Infer['widget'] = 'text' + if (uiSchema['ui:widget'] === 'textarea') widget = 'textarea' + if (uiSchema['ui:widget'] === 'password') widget = 'password' + if (schema.format === 'email') widget = 'email' + + const autocomplete = uiSchema['ui:autocomplete'] + return { + title: schema.title || '[No label]', + key: key, + widget, + placeholder: uiSchema['ui:placeholder'] || '', + autocomplete: isAutoCompleteValue(autocomplete) ? autocomplete : null, + defaultValue: (schema.default as string) || '', + maxLength: schema.maxLength || null + } +} + +function addFieldToRjsf( + properties: Record, + uiSchema: FormDefinition['ui'], + field: Infer.ListItem +) { + const key = field.key || field._id + console.log(key) + + const schema: JSONSchema7Definition = { + type: 'string', + title: field.title || '[No label]' + } + properties[key] = schema + if (field.maxLength) schema.maxLength = field.maxLength + if (field.defaultValue) schema.default = field.defaultValue + + const ui: any = {} + if (field.autocomplete) ui['ui:autocomplete'] = field.autocomplete + if (field.placeholder) ui['ui:placeholder'] = field.placeholder + if (field.widget === 'textarea') ui['ui:widget'] = 'textarea' + if (field.widget === 'email') schema.format = 'email' + if (field.widget === 'password') ui['ui:widget'] = 'password' + + if (Object.keys(ui).length > 0) { + uiSchema[key] = ui + } +} + +export const FormTextField = { + schema: Schema, + rjsfToField, + addFieldToRjsf +} satisfies FormFieldDefinition diff --git a/src/field/form/composed/FormArrayField.tsx b/src/field/form/composed/FormArrayField.tsx new file mode 100644 index 000000000..803ce64ac --- /dev/null +++ b/src/field/form/composed/FormArrayField.tsx @@ -0,0 +1,94 @@ +import type {Infer} from 'alinea/core/Infer' +import type {Schema} from 'alinea/core/Schema.js' +import {type} from 'alinea/core/Type' +import {generateKeyBetween} from 'alinea/core/util/FractionalIndexing' +import {path} from 'alinea/field/path' +import {text} from 'alinea/field/text/TextField' +import type {JSONSchema7, JSONSchema7Definition} from 'json-schema' +import {list} from '../../list.js' +import type {ComposedFormFieldDefinition, FormDefinition} from '../FormField.js' +import type {RjsfHandler} from '../RjsfHandler.js' + +type SchemaType = ReturnType + +function generateSchema(baseSchema: Schema) { + return type('Array', { + fields: { + title: text('Label', {required: true, width: 0.5}), + key: path('Key', {required: true, width: 0.5}), + items: list('Fields', {schema: baseSchema}) + } + }) +} + +export function rjsfToField( + key: string, + schema: FormDefinition['schema'], + uiSchema: FormDefinition['ui'], + handler: RjsfHandler +): Infer | undefined { + if (schema.type !== 'array') return undefined + if (!schema.items || schema.items === true) return undefined + if (Array.isArray(schema.items)) return undefined + if (schema.items.type !== 'object') return undefined + if (!schema.items.properties) return undefined + + const properties = schema.items.properties + const children: Infer.ListItem[] = [] + for (const entry of Object.entries(properties)) { + const [childKey, childSchema] = entry + const childUiSchema = uiSchema?.[key] || {} + if (!childSchema || childSchema === true) continue + + const previousChild = children[children.length - 1] + const _index = generateKeyBetween(previousChild?._index || null, null) + + const child = handler.rjsfToField(childKey, childSchema, childUiSchema) + if (child) { + children.push({...child, _index}) + } + } + + return { + title: schema.title || '[No label]', + key: key, + items: children + } +} + +export function addFieldToRjsf( + properties: Record, + uiSchema: FormDefinition['ui'], + field: Infer.ListItem, + handler: RjsfHandler +) { + const key = field.key || field._id + const childProperties: Record = {} + const childUiSchema: Record = {} + const schema: JSONSchema7Definition = { + type: 'array', + title: field.title || '[No label]', + items: { + type: 'object', + properties: childProperties + } + } + properties[key] = schema + + for (const child of field.items) { + handler.addFieldToRjsf(childProperties, childUiSchema, child) + } + + const ui: any = {} + ui[key] = {...childUiSchema} + + if (Object.keys(ui).length > 0) { + uiSchema[key] = ui + } +} + +export const FormArrayField = { + generateSchema, + rjsfToField, + addFieldToRjsf +} satisfies ComposedFormFieldDefinition diff --git a/src/field/form/composed/TwoColumnsField.tsx b/src/field/form/composed/TwoColumnsField.tsx new file mode 100644 index 000000000..42c99823c --- /dev/null +++ b/src/field/form/composed/TwoColumnsField.tsx @@ -0,0 +1,94 @@ +import type {Infer} from 'alinea/core/Infer' +import type {Schema} from 'alinea/core/Schema.js' +import {type} from 'alinea/core/Type' +import {generateKeyBetween} from 'alinea/core/util/FractionalIndexing' +import {path} from 'alinea/field/path' +import {text} from 'alinea/field/text/TextField' +import type {JSONSchema7, JSONSchema7Definition} from 'json-schema' +import {list} from '../../list.js' +import type {ComposedFormFieldDefinition, FormDefinition} from '../FormField.js' +import type {RjsfHandler} from '../RjsfHandler.js' + +type SchemaType = ReturnType + +function generateSchema(baseSchema: Schema) { + return type('Array', { + fields: { + title: text('Label', {required: true, width: 0.5}), + key: path('Key', {required: true, width: 0.5}), + items: list('Fields', {schema: baseSchema}) + } + }) +} + +export function rjsfToField( + key: string, + schema: FormDefinition['schema'], + uiSchema: FormDefinition['ui'], + handler: RjsfHandler +): Infer | undefined { + if (schema.type !== 'array') return undefined + if (!schema.items || schema.items === true) return undefined + if (Array.isArray(schema.items)) return undefined + if (schema.items.type !== 'object') return undefined + if (!schema.items.properties) return undefined + + const properties = schema.items.properties + const children: Infer.ListItem[] = [] + for (const entry of Object.entries(properties)) { + const [childKey, childSchema] = entry + const childUiSchema = uiSchema?.[key] || {} + if (!childSchema || childSchema === true) continue + + const previousChild = children[children.length - 1] + const _index = generateKeyBetween(previousChild?._index || null, null) + + const child = handler.rjsfToField(childKey, childSchema, childUiSchema) + if (child) { + children.push({...child, _index}) + } + } + + return { + title: schema.title || '[No label]', + key: key, + items: children + } +} + +export function addFieldToRjsf( + properties: Record, + uiSchema: FormDefinition['ui'], + field: Infer.ListItem, + handler: RjsfHandler +) { + const key = field.key || field._id + const childProperties: Record = {} + const childUiSchema: Record = {} + const schema: JSONSchema7Definition = { + type: 'array', + title: field.title || '[No label]', + items: { + type: 'object', + properties: childProperties + } + } + properties[key] = schema + + for (const child of field.items) { + handler.addFieldToRjsf(childProperties, childUiSchema, child) + } + + const ui: any = {} + ui[key] = {...childUiSchema} + + if (Object.keys(ui).length > 0) { + uiSchema[key] = ui + } +} + +export const TwoColumnsField = { + generateSchema, + rjsfToField, + addFieldToRjsf +} satisfies ComposedFormFieldDefinition diff --git a/src/field/form/utils/autoCompleteValues.ts b/src/field/form/utils/autoCompleteValues.ts new file mode 100644 index 000000000..7f159a391 --- /dev/null +++ b/src/field/form/utils/autoCompleteValues.ts @@ -0,0 +1,46 @@ +export const autoCompleteValues = { + "name": "Name", + "given-name": "Given name", + "family-name": "Family name", + "username": "Username", + "nickname": "Nickname", + + "email": "Email", + "new-password": "New password", + "current-password": "Current password", + "one-time-code": "One-time code", + + "tel": "Telephone", + "street-address": "Street address", + "address-line1": "Address line 1", + "address-line2": "Address line 2", + "postal-code": "Postal code", + "country": "Country (code)", + "country-name": "Country name", + "address-level2": "City", + "address-level1": "State / Province", + + "cc-number": "Credit card number", + "cc-exp": "Credit card expiry (MM/YY)", + "cc-exp-month": "Credit card expiry month", + "cc-exp-year": "Credit card expiry year", + + // Extra commonly used values + "organization": "Organization", + "birthdate": "Birthdate", + "bday-day": "Birthday (day)", + "bday-month": "Birthday (month)", + "bday-year": "Birthday (year)", + "url": "Website URL", + "sex": "Sex", + "gender-identity": "Gender identity", + + // On/Off toggles + "on": "Autocomplete on", + "off": "Autocomplete off" +} + +export function isAutoCompleteValue(value: string | undefined): value is keyof typeof autoCompleteValues { + if(!value) return false + return value in autoCompleteValues +} \ No newline at end of file