diff --git a/.changeset/config.json b/.changeset/config.json
index 34892af..db5bdd8 100644
--- a/.changeset/config.json
+++ b/.changeset/config.json
@@ -1,11 +1,11 @@
{
- "$schema": "https://unpkg.com/@changesets/config@3.0.4/schema.json",
- "changelog": "@changesets/cli/changelog",
- "commit": true,
- "fixed": [],
- "linked": [],
- "access": "restricted",
- "baseBranch": "main",
- "updateInternalDependencies": "patch",
- "ignore": []
+ "$schema": "https://unpkg.com/@changesets/config@3.0.4/schema.json",
+ "changelog": "@changesets/cli/changelog",
+ "commit": true,
+ "fixed": [],
+ "linked": [],
+ "access": "restricted",
+ "baseBranch": "main",
+ "updateInternalDependencies": "patch",
+ "ignore": []
}
diff --git a/.prettierrc.js b/.prettierrc.js
deleted file mode 100644
index 8a9d0e3..0000000
--- a/.prettierrc.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
- singleQuote: true,
-}
\ No newline at end of file
diff --git a/.syncpackrc.json b/.syncpackrc.json
new file mode 100644
index 0000000..11d4a18
--- /dev/null
+++ b/.syncpackrc.json
@@ -0,0 +1,11 @@
+{
+ "source": ["package.json", "*/*/package.json"],
+ "versionGroups": [
+ {
+ "label": "Use workspace protocol when developing local packages",
+ "dependencies": ["$LOCAL"],
+ "dependencyTypes": ["dev", "prod"],
+ "pinVersion": "workspace:*"
+ }
+ ]
+}
diff --git a/.tool-versions b/.tool-versions
deleted file mode 100644
index f31e6b0..0000000
--- a/.tool-versions
+++ /dev/null
@@ -1 +0,0 @@
-nodejs 20.18.0
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index ba236ff..a8bd4bb 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -1,9 +1,9 @@
{
- "recommendations": [
- "bradlc.vscode-tailwindcss",
- "dbaeumer.vscode-eslint",
- "esbenp.prettier-vscode",
- "astro-build.astro-vscode",
- "unifiedjs.vscode-mdx"
- ]
+ "recommendations": [
+ "bradlc.vscode-tailwindcss",
+ "dbaeumer.vscode-eslint",
+ "esbenp.prettier-vscode",
+ "astro-build.astro-vscode",
+ "unifiedjs.vscode-mdx"
+ ]
}
diff --git a/.vscode/launch.json b/.vscode/launch.json
index cfcc69c..9f74f62 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,12 +1,12 @@
{
- "version": "0.2.0",
- "configurations": [
- {
- "cwd": "${workspaceFolder}/apps/reactlit-docs",
- "command": "./node_modules/.bin/astro dev",
- "name": "Development server",
- "request": "launch",
- "type": "node-terminal"
- }
- ]
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "cwd": "${workspaceFolder}/apps/reactlit-docs",
+ "command": "./node_modules/.bin/astro dev",
+ "name": "Development server",
+ "request": "launch",
+ "type": "node-terminal"
+ }
+ ]
}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 976c263..da2d4c9 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,14 +1,29 @@
{
- "editor.codeActionsOnSave": {
- "source.fixAll.eslint": "explicit"
- },
- "typescript.tsdk": "node_modules/typescript/lib",
- "scss.lint.unknownAtRules": "ignore",
- "css.lint.unknownAtRules": "ignore",
- "editor.tabSize": 2,
- "[javascript][typescript][javscriptreact][typescriptreact][css][scss][json][jsonc][markdown][yaml]": {
- "editor.defaultFormatter": "esbenp.prettier-vscode"
- },
- "editor.formatOnSave": true,
- "files.eol": "\n"
+ "typescript.tsdk": "node_modules/typescript/lib",
+ "scss.lint.unknownAtRules": "ignore",
+ "css.lint.unknownAtRules": "ignore",
+ "editor.tabSize": 2,
+ "editor.defaultFormatter": "biomejs.biome",
+ "[javascript]": {
+ "editor.defaultFormatter": "biomejs.biome"
+ },
+ "[typescript]": {
+ "editor.defaultFormatter": "biomejs.biome"
+ },
+ "[typescriptreact]": {
+ "editor.defaultFormatter": "biomejs.biome"
+ },
+ "[json]": {
+ "editor.defaultFormatter": "biomejs.biome"
+ },
+ "eslint.enable": false,
+ "prettier.enable": false,
+ "editor.codeActionsOnSave": {
+ "source.organizeImports.biome": "explicit",
+ "source.fixAll.eslint": "never",
+ "source.fixAll.ts": "never",
+ "source.fixAll.biome": "explicit"
+ },
+ "editor.formatOnSave": true,
+ "files.eol": "\n"
}
diff --git a/apps/reactlit-docs/astro.config.mjs b/apps/reactlit-docs/astro.config.mjs
index 2b64793..e74ead0 100644
--- a/apps/reactlit-docs/astro.config.mjs
+++ b/apps/reactlit-docs/astro.config.mjs
@@ -1,48 +1,48 @@
-import { defineConfig } from 'astro/config';
-import starlight from '@astrojs/starlight';
-import react from '@astrojs/react';
+import react from "@astrojs/react";
+import starlight from "@astrojs/starlight";
+import { defineConfig } from "astro/config";
// https://astro.build/config
export default defineConfig({
- vite: {
- ssr: {
- noExternal: ['@radix-ui/themes'],
- },
- },
- base: 'reactlit',
- site: 'https://mshafir.github.io',
- integrations: [
- starlight({
- title: 'Reactlit',
- logo: {
- src: '/src/assets/ReactlitwText.png',
- alt: 'Reactlit',
- replacesTitle: true,
- },
- customCss: ['/src/styles/app.css'],
- social: {
- github: 'https://github.com/mshafir/reactlit',
- },
- sidebar: [
- {
- label: 'Guides',
- items: [
- { label: 'Getting Started', slug: 'guides/getting-started' },
- { label: 'Installation', slug: 'guides/installation' },
- { label: 'Basics', slug: 'guides/basics' },
- { label: 'Data Fetching', slug: 'guides/data-fetching' },
- { label: 'Wrappers', slug: 'guides/wrappers' },
- { label: 'Layout', slug: 'guides/layout' },
- { label: 'Managed State', slug: 'guides/managed-state' },
- { label: 'Defining Views', slug: 'guides/defining-views' },
- ],
- },
- // {
- // label: 'Reference',
- // autogenerate: { directory: 'reference' },
- // },
- ],
- }),
- react(),
- ],
+ vite: {
+ ssr: {
+ noExternal: ["@radix-ui/themes"],
+ },
+ },
+ base: "reactlit",
+ site: "https://mshafir.github.io",
+ integrations: [
+ starlight({
+ title: "Reactlit",
+ logo: {
+ src: "/src/assets/ReactlitwText.png",
+ alt: "Reactlit",
+ replacesTitle: true,
+ },
+ customCss: ["/src/styles/app.css"],
+ social: {
+ github: "https://github.com/mshafir/reactlit",
+ },
+ sidebar: [
+ {
+ label: "Guides",
+ items: [
+ { label: "Getting Started", slug: "guides/getting-started" },
+ { label: "Installation", slug: "guides/installation" },
+ { label: "Basics", slug: "guides/basics" },
+ { label: "Data Fetching", slug: "guides/data-fetching" },
+ { label: "Wrappers", slug: "guides/wrappers" },
+ { label: "Layout", slug: "guides/layout" },
+ { label: "Managed State", slug: "guides/managed-state" },
+ { label: "Defining Views", slug: "guides/defining-views" },
+ ],
+ },
+ // {
+ // label: 'Reference',
+ // autogenerate: { directory: 'reference' },
+ // },
+ ],
+ }),
+ react(),
+ ],
});
diff --git a/apps/reactlit-docs/ec.config.mjs b/apps/reactlit-docs/ec.config.mjs
index 1c37904..34f2db9 100644
--- a/apps/reactlit-docs/ec.config.mjs
+++ b/apps/reactlit-docs/ec.config.mjs
@@ -1,13 +1,13 @@
-import { pluginCollapsibleSections } from '@expressive-code/plugin-collapsible-sections';
-import { pluginLineNumbers } from '@expressive-code/plugin-line-numbers';
+import { pluginCollapsibleSections } from "@expressive-code/plugin-collapsible-sections";
+import { pluginLineNumbers } from "@expressive-code/plugin-line-numbers";
/** @type {import('@astrojs/starlight/expressive-code').StarlightExpressiveCodeOptions} */
export default {
- plugins: [pluginCollapsibleSections(), pluginLineNumbers()],
- defaultProps: {
- showLineNumbers: false,
- },
- styleOverrides: {
- codeFontSize: '12px',
- },
+ plugins: [pluginCollapsibleSections(), pluginLineNumbers()],
+ defaultProps: {
+ showLineNumbers: false,
+ },
+ styleOverrides: {
+ codeFontSize: "12px",
+ },
};
diff --git a/apps/reactlit-docs/package.json b/apps/reactlit-docs/package.json
index daae881..cdf5a3b 100644
--- a/apps/reactlit-docs/package.json
+++ b/apps/reactlit-docs/package.json
@@ -1,30 +1,30 @@
{
- "name": "reactlit-docs",
- "type": "module",
- "version": "0.0.16",
- "private": true,
- "scripts": {
- "dev": "astro dev",
- "start": "astro dev",
- "build": "astro build",
- "preview": "astro preview",
- "astro": "astro"
- },
- "dependencies": {
- "@astrojs/react": "^4.0.0",
- "@astrojs/starlight": "^0.29.2",
- "@expressive-code/plugin-collapsible-sections": "^0.38.3",
- "@expressive-code/plugin-line-numbers": "^0.38.3",
- "@radix-ui/themes": "^3.1.6",
- "@reactlit/core": "workspace:*",
- "@reactlit/radix": "workspace:*",
- "@tanstack/react-query": "^5.62.3",
- "@types/react": "^19.0.0",
- "@types/react-dom": "^19.0.0",
- "astro": "^4.16.10",
- "react": "^19.0.0",
- "react-dom": "^19.0.0",
- "sharp": "^0.32.5",
- "starlight-package-managers": "^0.8.0"
- }
-}
\ No newline at end of file
+ "name": "reactlit-docs",
+ "type": "module",
+ "version": "0.0.16",
+ "private": true,
+ "scripts": {
+ "dev": "astro dev",
+ "start": "astro dev",
+ "build": "astro build",
+ "preview": "astro preview",
+ "astro": "astro"
+ },
+ "dependencies": {
+ "@astrojs/react": "^4.0.0",
+ "@astrojs/starlight": "^0.29.2",
+ "@expressive-code/plugin-collapsible-sections": "^0.38.3",
+ "@expressive-code/plugin-line-numbers": "^0.38.3",
+ "@radix-ui/themes": "^3.1.6",
+ "@reactlit/core": "workspace:*",
+ "@reactlit/radix": "workspace:*",
+ "@tanstack/react-query": "^5.62.3",
+ "@types/react": "^19.0.1",
+ "@types/react-dom": "^19.0.2",
+ "astro": "^4.16.10",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "sharp": "^0.34.1",
+ "starlight-package-managers": "^0.8.0"
+ }
+}
diff --git a/apps/reactlit-docs/src/assets/reactlit.svg b/apps/reactlit-docs/src/assets/reactlit_logo.svg
similarity index 100%
rename from apps/reactlit-docs/src/assets/reactlit.svg
rename to apps/reactlit-docs/src/assets/reactlit_logo.svg
diff --git a/apps/reactlit-docs/src/content/config.ts b/apps/reactlit-docs/src/content/config.ts
index 45f60b0..d8d945f 100644
--- a/apps/reactlit-docs/src/content/config.ts
+++ b/apps/reactlit-docs/src/content/config.ts
@@ -1,5 +1,5 @@
-import { defineCollection } from 'astro:content';
-import { docsSchema } from '@astrojs/starlight/schema';
+import { defineCollection } from "astro:content";
+import { docsSchema } from "@astrojs/starlight/schema";
export const collections = {
docs: defineCollection({ schema: docsSchema() }),
diff --git a/apps/reactlit-docs/src/content/docs/index.mdx b/apps/reactlit-docs/src/content/docs/index.mdx
index 6cc74ac..cc395d3 100644
--- a/apps/reactlit-docs/src/content/docs/index.mdx
+++ b/apps/reactlit-docs/src/content/docs/index.mdx
@@ -5,7 +5,7 @@ template: splash
hero:
tagline: A faster way to build React apps
image:
- file: ../../assets/reactlit.svg
+ file: ../../assets/reactlit_logo.svg
actions:
- text: Getting Started
link: /reactlit/guides/getting-started/
diff --git a/apps/reactlit-docs/src/examples/apps/contact-list-basic-async.tsx b/apps/reactlit-docs/src/examples/apps/contact-list-basic-async.tsx
index 40c2af5..0d76536 100644
--- a/apps/reactlit-docs/src/examples/apps/contact-list-basic-async.tsx
+++ b/apps/reactlit-docs/src/examples/apps/contact-list-basic-async.tsx
@@ -1,56 +1,56 @@
-import { Button } from '@radix-ui/themes';
-import { type ReactlitContext } from '@reactlit/core';
-import { Inputs, Label } from '@reactlit/radix';
-import { TopRightLoader } from '../components/loader';
-import { ContactsMockService } from '../mocks/contacts';
+import { Button } from "@radix-ui/themes";
+import { type ReactlitContext } from "@reactlit/core";
+import { Inputs, Label } from "@reactlit/radix";
+import { TopRightLoader } from "../components/loader";
+import { ContactsMockService } from "../mocks/contacts";
// add a delay to the mock API
const api = new ContactsMockService([], 500);
export async function ContactListApp(app: ReactlitContext) {
- // wrap a loader around the contacts fetch
- app.display('loading-items', );
- const contacts = await api.getContacts();
- app.display('loading-items', undefined);
- app.display(
-
- );
- const selectedContact = app.view(
- 'selectedContact',
- Inputs.Table(contacts, {
- getRowId: (contact) => contact.id,
- columns: ['name', 'email'],
- })
- );
- if (!selectedContact) return;
- app.display(
Selected Contact Details
);
- if (app.changed('selectedContact')) {
- app.set('name', selectedContact.name);
- app.set('email', selectedContact.email);
- }
- const updates = {
- name: app.view('name', Label('Name'), Inputs.Text()),
- email: app.view('email', Label('Email'), Inputs.Text()),
- };
- // if you wish, you can use an AsyncButton view for a button that
- // has a loading state during async operations
- app.view(
- 'updating',
- Inputs.AsyncButton(
- async () => {
- await api.updateContact(selectedContact.id, updates);
- app.trigger();
- },
- {
- content: 'Update',
- }
- )
- );
+ // wrap a loader around the contacts fetch
+ app.display("loading-items", );
+ const contacts = await api.getContacts();
+ app.display("loading-items", undefined);
+ app.display(
+ ,
+ );
+ const selectedContact = app.view(
+ "selectedContact",
+ Inputs.Table(contacts, {
+ getRowId: (contact) => contact.id,
+ columns: ["name", "email"],
+ }),
+ );
+ if (!selectedContact) return;
+ app.display(Selected Contact Details
);
+ if (app.changed("selectedContact")) {
+ app.set("name", selectedContact.name);
+ app.set("email", selectedContact.email);
+ }
+ const updates = {
+ name: app.view("name", Label("Name"), Inputs.Text()),
+ email: app.view("email", Label("Email"), Inputs.Text()),
+ };
+ // if you wish, you can use an AsyncButton view for a button that
+ // has a loading state during async operations
+ app.view(
+ "updating",
+ Inputs.AsyncButton(
+ async () => {
+ await api.updateContact(selectedContact.id, updates);
+ app.trigger();
+ },
+ {
+ content: "Update",
+ },
+ ),
+ );
}
diff --git a/apps/reactlit-docs/src/examples/apps/contact-list.tsx b/apps/reactlit-docs/src/examples/apps/contact-list.tsx
index 3397c8d..344bc6c 100644
--- a/apps/reactlit-docs/src/examples/apps/contact-list.tsx
+++ b/apps/reactlit-docs/src/examples/apps/contact-list.tsx
@@ -1,46 +1,46 @@
-import { Button } from '@radix-ui/themes';
-import { type ReactlitContext } from '@reactlit/core';
-import { Inputs, Label } from '@reactlit/radix';
-import { ContactMockApi as api } from '../mocks/contacts';
+import { Button } from "@radix-ui/themes";
+import { type ReactlitContext } from "@reactlit/core";
+import { Inputs, Label } from "@reactlit/radix";
+import { ContactMockApi as api } from "../mocks/contacts";
export async function ContactListApp(app: ReactlitContext) {
- const contacts = await api.getContacts();
- app.display(
-
- );
- const selectedContact = app.view(
- 'selectedContact',
- Inputs.Table(contacts, {
- getRowId: (contact) => contact.id,
- columns: ['name', 'email'],
- })
- );
- if (!selectedContact) return;
- app.display(Selected Contact Details
);
- if (app.changed('selectedContact')) {
- app.set('name', selectedContact.name);
- app.set('email', selectedContact.email);
- }
- // the built-in FormView allows you to group inputs together
- const updates = {
- name: app.view('name', Label('Name'), Inputs.Text()),
- email: app.view('email', Label('Email'), Inputs.Text()),
- };
- app.display(
-
- );
+ const contacts = await api.getContacts();
+ app.display(
+ ,
+ );
+ const selectedContact = app.view(
+ "selectedContact",
+ Inputs.Table(contacts, {
+ getRowId: (contact) => contact.id,
+ columns: ["name", "email"],
+ }),
+ );
+ if (!selectedContact) return;
+ app.display(Selected Contact Details
);
+ if (app.changed("selectedContact")) {
+ app.set("name", selectedContact.name);
+ app.set("email", selectedContact.email);
+ }
+ // the built-in FormView allows you to group inputs together
+ const updates = {
+ name: app.view("name", Label("Name"), Inputs.Text()),
+ email: app.view("email", Label("Email"), Inputs.Text()),
+ };
+ app.display(
+ ,
+ );
}
diff --git a/apps/reactlit-docs/src/examples/components/loader.tsx b/apps/reactlit-docs/src/examples/components/loader.tsx
index 3a76e93..e8dfa95 100644
--- a/apps/reactlit-docs/src/examples/components/loader.tsx
+++ b/apps/reactlit-docs/src/examples/components/loader.tsx
@@ -1,18 +1,18 @@
-import { Spinner, Badge } from '@radix-ui/themes';
+import { Badge, Spinner } from "@radix-ui/themes";
export function TopRightLoader({ text }: { text: string }) {
- return (
-
- {text}
-
- );
+ return (
+
+ {text}
+
+ );
}
diff --git a/apps/reactlit-docs/src/examples/contact-list-basic-async.tsx b/apps/reactlit-docs/src/examples/contact-list-basic-async.tsx
index 2a96106..5b0cfa1 100644
--- a/apps/reactlit-docs/src/examples/contact-list-basic-async.tsx
+++ b/apps/reactlit-docs/src/examples/contact-list-basic-async.tsx
@@ -1,6 +1,6 @@
-import { ContactListApp } from './apps/contact-list-basic-async';
-import RenderRadixApp from './render-radix-app';
+import { ContactListApp } from "./apps/contact-list-basic-async";
+import RenderRadixApp from "./render-radix-app";
export default function ContactList() {
- return ;
+ return ;
}
diff --git a/apps/reactlit-docs/src/examples/contact-list-data-fetch.tsx b/apps/reactlit-docs/src/examples/contact-list-data-fetch.tsx
index 41d9164..bddfab4 100644
--- a/apps/reactlit-docs/src/examples/contact-list-data-fetch.tsx
+++ b/apps/reactlit-docs/src/examples/contact-list-data-fetch.tsx
@@ -1,108 +1,108 @@
-import { Button, Theme } from '@radix-ui/themes';
-import '@radix-ui/themes/styles.css';
-import { DataFetchingPlugin, useReactlit } from '@reactlit/core';
-import { Inputs, Label } from '@reactlit/radix';
-import { TopRightLoader } from './components/loader';
-import { ContactsMockService } from './mocks/contacts';
+import { Button, Theme } from "@radix-ui/themes";
+import "@radix-ui/themes/styles.css";
+import { DataFetchingPlugin, useReactlit } from "@reactlit/core";
+import { Inputs, Label } from "@reactlit/radix";
+import { TopRightLoader } from "./components/loader";
+import { ContactsMockService } from "./mocks/contacts";
export default function ContactList() {
- return (
-
-
-
- );
+ return (
+
+
+
+ );
}
// slow down the mock API to demo user experience with a slow API
const api = new ContactsMockService([], 1000);
const ContactListApp = () => {
- const Reactlit = useReactlit(DataFetchingPlugin);
- return (
-
- {async (app) => {
- //
- // create a fetcher for the contacts with a cache key of ['contacts']
- const contactsFetcher = app.fetcher(['contacts'], () =>
- api.getContacts()
- );
+ const Reactlit = useReactlit(DataFetchingPlugin);
+ return (
+
+ {async (app) => {
+ //
+ // create a fetcher for the contacts with a cache key of ['contacts']
+ const contactsFetcher = app.fetcher(["contacts"], () =>
+ api.getContacts(),
+ );
- if (contactsFetcher.isFetching()) {
- app.display('loader', );
- }
+ if (contactsFetcher.isFetching()) {
+ app.display("loader", );
+ }
- // get the current contacts synchronously, back off to empty list if null
- const contacts = contactsFetcher.get() ?? [];
+ // get the current contacts synchronously, back off to empty list if null
+ const contacts = contactsFetcher.get() ?? [];
- app.display(
-
- );
- const selectedContact = app.view(
- 'selectedContact',
- Inputs.Table(contacts, {
- getRowId: (contact) => contact.id,
- columns: ['name', 'email'],
- })
- );
- if (!selectedContact) return;
- app.display(
- Selected Contact Details
- );
- if (app.changed('selectedContact')) {
- app.set('name', selectedContact.name);
- app.set('email', selectedContact.email);
- }
- const updates = {
- name: app.view('name', Label('Name'), Inputs.Text()),
- email: app.view('email', Label('Email'), Inputs.Text()),
- };
- app.display(
-
- );
- }}
-
- );
+ app.display(
+ ,
+ );
+ const selectedContact = app.view(
+ "selectedContact",
+ Inputs.Table(contacts, {
+ getRowId: (contact) => contact.id,
+ columns: ["name", "email"],
+ }),
+ );
+ if (!selectedContact) return;
+ app.display(
+ Selected Contact Details
,
+ );
+ if (app.changed("selectedContact")) {
+ app.set("name", selectedContact.name);
+ app.set("email", selectedContact.email);
+ }
+ const updates = {
+ name: app.view("name", Label("Name"), Inputs.Text()),
+ email: app.view("email", Label("Email"), Inputs.Text()),
+ };
+ app.display(
+ ,
+ );
+ }}
+
+ );
};
diff --git a/apps/reactlit-docs/src/examples/contact-list-react.tsx b/apps/reactlit-docs/src/examples/contact-list-react.tsx
index 4761119..46a6ccc 100644
--- a/apps/reactlit-docs/src/examples/contact-list-react.tsx
+++ b/apps/reactlit-docs/src/examples/contact-list-react.tsx
@@ -1,126 +1,127 @@
-import { Button, Theme } from '@radix-ui/themes';
-import '@radix-ui/themes/styles.css';
+import { Button, Theme } from "@radix-ui/themes";
+import "@radix-ui/themes/styles.css";
import {
- SingleTableInputViewComponent,
- TextInputComponent,
-} from '@reactlit/radix';
+ SingleTableInputViewComponent,
+ TextInputComponent,
+} from "@reactlit/radix";
import {
- QueryClient,
- QueryClientProvider,
- useMutation,
- useQuery,
- useQueryClient,
-} from '@tanstack/react-query';
-import { useEffect, useMemo, useState } from 'react';
-import { TopRightLoader } from './components/loader';
-import { ContactsMockService, type Contact } from './mocks/contacts';
+ QueryClient,
+ QueryClientProvider,
+ useMutation,
+ useQuery,
+ useQueryClient,
+} from "@tanstack/react-query";
+import { useEffect, useMemo, useState } from "react";
+import { TopRightLoader } from "./components/loader";
+import { type Contact, ContactsMockService } from "./mocks/contacts";
const api = new ContactsMockService([], 1000);
const queryClient = new QueryClient();
export default function ContactList() {
- return (
-
-
-
-
-
- );
+ return (
+
+
+
+
+
+ );
}
function ContactListApp() {
- const [name, setName] = useState('');
- const [email, setEmail] = useState('');
- const client = useQueryClient();
- const { data: contacts, isFetching } = useQuery({
- queryKey: ['contacts'],
- queryFn: () => api.getContacts(),
- });
- const { mutateAsync: addContact, isPending: addingContact } = useMutation({
- mutationFn: async () => await api.addContact(),
- onSuccess: async (newContact) => {
- await client.invalidateQueries({ queryKey: ['contacts'] });
- setSelectedContactId(newContact.id);
- },
- });
- const { mutateAsync: updateContact, isPending: updatingContact } =
- useMutation({
- mutationFn: (contact: Contact) => api.updateContact(contact.id, contact),
- onSuccess: (data) => {
- client.setQueryData(['contacts'], (contacts: Contact[]) =>
- contacts.map((c) => (c.id === data.id ? data : c))
- );
- },
- });
- const [selectedContactId, setSelectedContactId] = useState<
- string | undefined
- >();
- const selectedContact = useMemo(
- () => contacts?.find((c) => c.id === selectedContactId),
- [contacts, selectedContactId]
- );
- useEffect(() => {
- if (selectedContact) {
- setName(selectedContact.name);
- setEmail(selectedContact.email);
- }
- }, [selectedContact, setName, setEmail]);
- return (
-
- {isFetching && }
- {!isFetching && addingContact && (
-
- )}
- {!isFetching && updatingContact && (
-
- )}
-
- setSelectedContactId(id)}
- getRowId={(c) => c.id}
- display={() => {}}
- view={() => undefined as any}
- />
- {selectedContact && (
- <>
- Selected Contact Details
- {}}
- view={() => undefined as any}
- />
- {}}
- view={() => undefined as any}
- />
-
- >
- )}
-
- );
+ const [name, setName] = useState("");
+ const [email, setEmail] = useState("");
+ const client = useQueryClient();
+ const { data: contacts, isFetching } = useQuery({
+ queryKey: ["contacts"],
+ queryFn: () => api.getContacts(),
+ });
+ const { mutateAsync: addContact, isPending: addingContact } = useMutation({
+ mutationFn: async () => await api.addContact(),
+ onSuccess: async (newContact) => {
+ await client.invalidateQueries({ queryKey: ["contacts"] });
+ setSelectedContactId(newContact.id);
+ },
+ });
+ const { mutateAsync: updateContact, isPending: updatingContact } =
+ useMutation({
+ mutationFn: (contact: Contact) => api.updateContact(contact.id, contact),
+ onSuccess: (data) => {
+ client.setQueryData(["contacts"], (contacts: Contact[]) =>
+ contacts.map((c) => (c.id === data.id ? data : c)),
+ );
+ },
+ });
+ const [selectedContactId, setSelectedContactId] = useState<
+ string | undefined
+ >();
+ const selectedContact = useMemo(
+ () => contacts?.find((c) => c.id === selectedContactId),
+ [contacts, selectedContactId],
+ );
+ useEffect(() => {
+ if (selectedContact) {
+ setName(selectedContact.name);
+ setEmail(selectedContact.email);
+ }
+ }, [selectedContact]);
+ return (
+
+ {isFetching && }
+ {!isFetching && addingContact && (
+
+ )}
+ {!isFetching && updatingContact && (
+
+ )}
+
+ setSelectedContactId(id)}
+ getRowId={(c) => c.id}
+ display={() => {}}
+ // biome-ignore lint/suspicious/noExplicitAny:
+ view={() => undefined as any}
+ />
+ {selectedContact && (
+ <>
+ Selected Contact Details
+ {}}
+ // biome-ignore lint/suspicious/noExplicitAny:
+ view={() => undefined as any}
+ />
+ {}}
+ // biome-ignore lint/suspicious/noExplicitAny:
+ view={() => undefined as any}
+ />
+
+ >
+ )}
+
+ );
}
diff --git a/apps/reactlit-docs/src/examples/contact-list.tsx b/apps/reactlit-docs/src/examples/contact-list.tsx
index 6e66def..f87c629 100644
--- a/apps/reactlit-docs/src/examples/contact-list.tsx
+++ b/apps/reactlit-docs/src/examples/contact-list.tsx
@@ -1,12 +1,12 @@
-import { Theme } from '@radix-ui/themes';
-import { Reactlit } from '@reactlit/core';
-import '@radix-ui/themes/styles.css';
-import { ContactListApp } from './apps/contact-list';
+import { Theme } from "@radix-ui/themes";
+import { Reactlit } from "@reactlit/core";
+import "@radix-ui/themes/styles.css";
+import { ContactListApp } from "./apps/contact-list";
export default function ContactList() {
- return (
-
- {ContactListApp}
-
- );
+ return (
+
+ {ContactListApp}
+
+ );
}
diff --git a/apps/reactlit-docs/src/examples/hello-world-react.tsx b/apps/reactlit-docs/src/examples/hello-world-react.tsx
index f04a773..21f07f9 100644
--- a/apps/reactlit-docs/src/examples/hello-world-react.tsx
+++ b/apps/reactlit-docs/src/examples/hello-world-react.tsx
@@ -1,19 +1,19 @@
-import { useState } from 'react';
+import { useState } from "react";
export default function HelloWorldReact() {
- const [name, setName] = useState('');
- let display = Hello {name}
;
- if (!name) {
- display = Name is required
;
- }
- return (
-
- setName(e.target.value)}
- placeholder="Enter name"
- />
- {display}
-
- );
+ const [name, setName] = useState("");
+ let display = Hello {name}
;
+ if (!name) {
+ display = Name is required
;
+ }
+ return (
+
+ setName(e.target.value)}
+ placeholder="Enter name"
+ />
+ {display}
+
+ );
}
diff --git a/apps/reactlit-docs/src/examples/hello-world.tsx b/apps/reactlit-docs/src/examples/hello-world.tsx
index 99038d0..2337ad1 100644
--- a/apps/reactlit-docs/src/examples/hello-world.tsx
+++ b/apps/reactlit-docs/src/examples/hello-world.tsx
@@ -1,16 +1,16 @@
-import { Reactlit } from '@reactlit/core';
-import { TextInput } from './inputs/basic-text-input';
+import { Reactlit } from "@reactlit/core";
+import { TextInput } from "./inputs/basic-text-input";
export default function HelloWorld() {
- return (
-
- {async ({ display, view }) => {
- const name = view('name', TextInput);
- if (!name) {
- throw new Error('Name is required');
- }
- display(Hello {name}
);
- }}
-
- );
+ return (
+
+ {async ({ display, view }) => {
+ const name = view("name", TextInput);
+ if (!name) {
+ throw new Error("Name is required");
+ }
+ display(Hello {name}
);
+ }}
+
+ );
}
diff --git a/apps/reactlit-docs/src/examples/inputs/basic-text-input.tsx b/apps/reactlit-docs/src/examples/inputs/basic-text-input.tsx
index 88e885b..973af99 100644
--- a/apps/reactlit-docs/src/examples/inputs/basic-text-input.tsx
+++ b/apps/reactlit-docs/src/examples/inputs/basic-text-input.tsx
@@ -1,12 +1,12 @@
-import { defineView } from '@reactlit/core';
+import { defineView } from "@reactlit/core";
export const TextInput = defineView(({ value, setValue, stateKey }) => (
- setValue(e.target.value)}
- placeholder={`Enter ${stateKey}`}
- />
+ setValue(e.target.value)}
+ placeholder={`Enter ${stateKey}`}
+ />
));
diff --git a/apps/reactlit-docs/src/examples/layout-example.tsx b/apps/reactlit-docs/src/examples/layout-example.tsx
index 69989ae..a7cb504 100644
--- a/apps/reactlit-docs/src/examples/layout-example.tsx
+++ b/apps/reactlit-docs/src/examples/layout-example.tsx
@@ -1,39 +1,39 @@
-import { LayoutView, useReactlit } from '@reactlit/core';
-import { TextInput } from './inputs/basic-text-input';
+import { LayoutView, useReactlit } from "@reactlit/core";
+import { TextInput } from "./inputs/basic-text-input";
export default function LayoutExample() {
- const Reactlit = useReactlit();
- return (
-
- {async ({ view }) => {
- const [col1, col2, col3] = view(
- 'cols',
- ,
- // the second argument here wraps each slot in a div so that they show
- // up as a single grid column in the layout
- LayoutView(3, )
- );
+ const Reactlit = useReactlit();
+ return (
+
+ {async ({ view }) => {
+ const [col1, col2, col3] = view(
+ "cols",
+ ,
+ // the second argument here wraps each slot in a div so that they show
+ // up as a single grid column in the layout
+ LayoutView(3, ),
+ );
- col1.display('First Name');
- const first = col1.view('first', TextInput);
+ col1.display("First Name");
+ const first = col1.view("first", TextInput);
- col2.display('Last Name');
- const last = col2.view('last', TextInput);
+ col2.display("Last Name");
+ const last = col2.view("last", TextInput);
- col3.display('Hello to');
- col3.display(
-
- {first} {last}
-
- );
- }}
-
- );
+ col3.display("Hello to");
+ col3.display(
+
+ {first} {last}
+
,
+ );
+ }}
+
+ );
}
diff --git a/apps/reactlit-docs/src/examples/layouts/three-column-layout.tsx b/apps/reactlit-docs/src/examples/layouts/three-column-layout.tsx
index f890401..b35dc4e 100644
--- a/apps/reactlit-docs/src/examples/layouts/three-column-layout.tsx
+++ b/apps/reactlit-docs/src/examples/layouts/three-column-layout.tsx
@@ -1,25 +1,25 @@
-import { defineLayout } from '@reactlit/core';
+import { defineLayout } from "@reactlit/core";
export const ThreeColumnLayout = defineLayout(
- 3,
- ({ slots: [Slot1, Slot2, Slot3] }) => (
-
- )
+ 3,
+ ({ slots: [Slot1, Slot2, Slot3] }) => (
+
+ ),
);
diff --git a/apps/reactlit-docs/src/examples/mocks/contacts.ts b/apps/reactlit-docs/src/examples/mocks/contacts.ts
index f122a06..3685465 100644
--- a/apps/reactlit-docs/src/examples/mocks/contacts.ts
+++ b/apps/reactlit-docs/src/examples/mocks/contacts.ts
@@ -1,50 +1,50 @@
// This is mocking a backend API for demo purposes
-import { wait } from '../utils/wait';
+import { wait } from "../utils/wait";
export type Contact = {
- id: string;
- name: string;
- email: string;
+ id: string;
+ name: string;
+ email: string;
};
// we add a delay to these to simulate a network request
export class ContactsMockService {
- constructor(
- private contacts: Contact[],
- private readonly delay: number = 0
- ) {}
+ constructor(
+ private contacts: Contact[],
+ private readonly delay: number = 0,
+ ) {}
- async getContacts() {
- await wait(this.delay);
- return this.contacts;
- }
+ async getContacts() {
+ await wait(this.delay);
+ return this.contacts;
+ }
- async addContact(contact?: Partial>) {
- await wait(this.delay);
- const newContact = {
- name: `New Contact ${this.contacts.length + 1}`,
- email: `contact${this.contacts.length + 1}@example.com`,
- ...(contact ?? {}),
- id: `contact-${this.contacts.length + 1}`,
- };
- this.contacts = [...this.contacts, newContact];
- return newContact;
- }
+ async addContact(contact?: Partial>) {
+ await wait(this.delay);
+ const newContact = {
+ name: `New Contact ${this.contacts.length + 1}`,
+ email: `contact${this.contacts.length + 1}@example.com`,
+ ...(contact ?? {}),
+ id: `contact-${this.contacts.length + 1}`,
+ };
+ this.contacts = [...this.contacts, newContact];
+ return newContact;
+ }
- async updateContact(id: string, contact: Partial) {
- await wait(this.delay);
- const index = this.contacts.findIndex((c) => c.id === id);
- if (index === -1) {
- throw new Error('Contact not found');
- }
- this.contacts = [
- ...this.contacts.slice(0, index),
- { ...this.contacts[index], ...contact },
- ...this.contacts.slice(index + 1),
- ];
- return this.contacts[index];
- }
+ async updateContact(id: string, contact: Partial) {
+ await wait(this.delay);
+ const index = this.contacts.findIndex((c) => c.id === id);
+ if (index === -1) {
+ throw new Error("Contact not found");
+ }
+ this.contacts = [
+ ...this.contacts.slice(0, index),
+ { ...this.contacts[index], ...contact },
+ ...this.contacts.slice(index + 1),
+ ];
+ return this.contacts[index];
+ }
}
export const ContactMockApi = new ContactsMockService([], 0);
diff --git a/apps/reactlit-docs/src/examples/render-radix-app.tsx b/apps/reactlit-docs/src/examples/render-radix-app.tsx
index 1359620..eae1c8c 100644
--- a/apps/reactlit-docs/src/examples/render-radix-app.tsx
+++ b/apps/reactlit-docs/src/examples/render-radix-app.tsx
@@ -1,13 +1,13 @@
-import { Theme } from '@radix-ui/themes';
-import '@radix-ui/themes/styles.css';
-import { Reactlit, type ReactlitFunction } from '@reactlit/core';
+import { Theme } from "@radix-ui/themes";
+import "@radix-ui/themes/styles.css";
+import { Reactlit, type ReactlitFunction } from "@reactlit/core";
export default function RenderRadixApp({ app }: { app: ReactlitFunction }) {
- return (
-
-
- {app}
-
-
- );
+ return (
+
+
+ {app}
+
+
+ );
}
diff --git a/apps/reactlit-docs/src/examples/transform-view.tsx b/apps/reactlit-docs/src/examples/transform-view.tsx
index 498eeae..4981df7 100644
--- a/apps/reactlit-docs/src/examples/transform-view.tsx
+++ b/apps/reactlit-docs/src/examples/transform-view.tsx
@@ -1,36 +1,36 @@
-import { Reactlit } from '@reactlit/core';
-import { defineTransformView } from '@reactlit/core';
+import { Reactlit } from "@reactlit/core";
+import { defineTransformView } from "@reactlit/core";
export const FilterInput = (values: string[]) =>
- defineTransformView(
- ({ value, setValue, stateKey }) => (
- setValue(e.target.value)}
- placeholder={`Search`}
- />
- ),
- (props) =>
- values.filter(
- (value) =>
- !props.value ||
- value.toLowerCase().includes(props.value.toLowerCase())
- )
- );
+ defineTransformView(
+ ({ value, setValue, stateKey }) => (
+ setValue(e.target.value)}
+ placeholder="Search"
+ />
+ ),
+ (props) =>
+ values.filter(
+ (value) =>
+ !props.value ||
+ value.toLowerCase().includes(props.value.toLowerCase()),
+ ),
+ );
export default function HelloWorld() {
- return (
-
- {async ({ display, view }) => {
- const users = ['Michael', 'Mary', 'John', 'Jane', 'Jim', 'Jill'];
- display('Search for a user: ');
- const filteredUsers = view('filteredUsers', FilterInput(users));
- if (filteredUsers.length === 0) {
- throw new Error('No matching users found');
- }
- display(You are filtering to {filteredUsers.join(', ')}
);
- }}
-
- );
+ return (
+
+ {async ({ display, view }) => {
+ const users = ["Michael", "Mary", "John", "Jane", "Jim", "Jill"];
+ display("Search for a user: ");
+ const filteredUsers = view("filteredUsers", FilterInput(users));
+ if (filteredUsers.length === 0) {
+ throw new Error("No matching users found");
+ }
+ display(You are filtering to {filteredUsers.join(", ")}
);
+ }}
+
+ );
}
diff --git a/apps/reactlit-docs/src/examples/utils/wait.ts b/apps/reactlit-docs/src/examples/utils/wait.ts
index cbd907e..76d5de3 100644
--- a/apps/reactlit-docs/src/examples/utils/wait.ts
+++ b/apps/reactlit-docs/src/examples/utils/wait.ts
@@ -1,3 +1,3 @@
export async function wait(ms: number) {
- return new Promise((resolve) => setTimeout(resolve, ms));
+ return new Promise((resolve) => setTimeout(resolve, ms));
}
diff --git a/apps/reactlit-docs/tsconfig.json b/apps/reactlit-docs/tsconfig.json
index 032ad64..40dde9b 100644
--- a/apps/reactlit-docs/tsconfig.json
+++ b/apps/reactlit-docs/tsconfig.json
@@ -1,7 +1,7 @@
{
- "extends": "astro/tsconfigs/strict",
- "compilerOptions": {
- "jsx": "react-jsx",
- "jsxImportSource": "react"
- }
-}
\ No newline at end of file
+ "extends": "astro/tsconfigs/strict",
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "jsxImportSource": "react"
+ }
+}
diff --git a/apps/reactlit-examples/.eslintrc.json b/apps/reactlit-examples/.eslintrc.json
deleted file mode 100644
index 3722418..0000000
--- a/apps/reactlit-examples/.eslintrc.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": ["next/core-web-vitals", "next/typescript"]
-}
diff --git a/apps/reactlit-examples/next.config.ts b/apps/reactlit-examples/next.config.ts
index 9487e69..5bb73a1 100644
--- a/apps/reactlit-examples/next.config.ts
+++ b/apps/reactlit-examples/next.config.ts
@@ -1,9 +1,14 @@
-import type { NextConfig } from 'next';
+import type { NextConfig } from "next";
const nextConfig: NextConfig = {
- output: 'export',
- transpilePackages: ['@reactlit/core', '@reactlit/vanilla'],
- reactStrictMode: true,
+ output: "export",
+ transpilePackages: ["@reactlit/core", "@reactlit/vanilla"],
+ reactStrictMode: true,
+ eslint: {
+ // Warning: This allows production builds to successfully complete even if
+ // your project has ESLint errors.
+ ignoreDuringBuilds: true,
+ },
};
export default nextConfig;
diff --git a/apps/reactlit-examples/package.json b/apps/reactlit-examples/package.json
index ac4c75a..4862608 100644
--- a/apps/reactlit-examples/package.json
+++ b/apps/reactlit-examples/package.json
@@ -1,37 +1,35 @@
{
- "name": "reactlit-examples",
- "version": "0.0.16",
- "private": true,
- "scripts": {
- "dev": "next dev --turbopack",
- "build": "next build",
- "start": "next start",
- "lint": "next lint"
- },
- "dependencies": {
- "@radix-ui/themes": "^3.1.6",
- "@reactlit/core": "workspace:*",
- "@reactlit/radix": "workspace:*",
- "@reactlit/vanilla": "workspace:*",
- "@tanstack/react-query": "^5.62.3",
- "clsx": "^2.1.1",
- "lucide-react": "^0.468.0",
- "next": "15.1.0",
- "next-themes": "^0.4.4",
- "react": "^19.0.0",
- "react-dom": "^19.0.0",
- "tailwind-merge": "^2.5.5",
- "tunnel-rat": "^0.1.2"
- },
- "devDependencies": {
- "@eslint/eslintrc": "^3",
- "@types/node": "^20",
- "@types/react": "^19",
- "@types/react-dom": "^19",
- "eslint": "^9",
- "eslint-config-next": "15.1.0",
- "postcss": "^8",
- "tailwindcss": "^3.4.1",
- "typescript": "^5"
- }
-}
\ No newline at end of file
+ "name": "reactlit-examples",
+ "version": "0.0.16",
+ "private": true,
+ "scripts": {
+ "dev": "next dev --turbopack",
+ "build": "next build",
+ "start": "next start",
+ "lint": "biome check --write"
+ },
+ "dependencies": {
+ "@radix-ui/themes": "^3.1.6",
+ "@reactlit/core": "workspace:*",
+ "@reactlit/radix": "workspace:*",
+ "@reactlit/vanilla": "workspace:*",
+ "@tanstack/react-query": "^5.62.3",
+ "clsx": "^2.1.1",
+ "lucide-react": "^0.468.0",
+ "next": "15.1.0",
+ "next-themes": "^0.4.4",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "tailwind-merge": "^2.5.5",
+ "tunnel-rat": "^0.1.2"
+ },
+ "devDependencies": {
+ "@biomejs/biome": "1.9.4",
+ "@types/node": "^22.10.10",
+ "@types/react": "^19.0.1",
+ "@types/react-dom": "^19.0.2",
+ "postcss": "^8",
+ "tailwindcss": "^3.4.1",
+ "typescript": "*"
+ }
+}
diff --git a/apps/reactlit-examples/postcss.config.mjs b/apps/reactlit-examples/postcss.config.mjs
index 1a69fd2..f6c3605 100644
--- a/apps/reactlit-examples/postcss.config.mjs
+++ b/apps/reactlit-examples/postcss.config.mjs
@@ -1,8 +1,8 @@
/** @type {import('postcss-load-config').Config} */
const config = {
- plugins: {
- tailwindcss: {},
- },
+ plugins: {
+ tailwindcss: {},
+ },
};
export default config;
diff --git a/apps/reactlit-examples/src/components/debug-toggle.tsx b/apps/reactlit-examples/src/components/debug-toggle.tsx
index a59017a..35a1b58 100644
--- a/apps/reactlit-examples/src/components/debug-toggle.tsx
+++ b/apps/reactlit-examples/src/components/debug-toggle.tsx
@@ -1,51 +1,51 @@
-'use client';
-import { Button, Tooltip } from '@radix-ui/themes';
-import { Wrapper } from '@reactlit/core';
-import { Bug, BugOff } from 'lucide-react';
-import { createContext, useContext, useState } from 'react';
+"use client";
+import { Button, Tooltip } from "@radix-ui/themes";
+import { Wrapper } from "@reactlit/core";
+import { Bug, BugOff } from "lucide-react";
+import { createContext, useContext, useState } from "react";
type DebugContextType = {
- debug: boolean;
- setDebug: (debug: boolean) => void;
+ debug: boolean;
+ setDebug: (debug: boolean) => void;
};
const DebugContext = createContext({
- debug: false,
- setDebug: () => {},
+ debug: false,
+ setDebug: () => {},
});
export const DebugToggle = () => {
- const { debug, setDebug } = useContext(DebugContext);
- return (
-
-
-
- );
+ const { debug, setDebug } = useContext(DebugContext);
+ return (
+
+
+
+ );
};
export const DebugProvider = ({ children }: { children: React.ReactNode }) => {
- const [debug, setDebug] = useState(false);
- return (
-
- {children}
-
- );
+ const [debug, setDebug] = useState(false);
+ return (
+
+ {children}
+
+ );
};
export function useDebug() {
- const { debug } = useContext(DebugContext);
- return debug;
+ const { debug } = useContext(DebugContext);
+ return debug;
}
export const Debug: Wrapper = ({ children, stateKey }) => {
- const debug = useDebug();
- if (!debug) return children;
- return (
-
-
{stateKey}
-
{children}
-
- );
+ const debug = useDebug();
+ if (!debug) return children;
+ return (
+
+
{stateKey}
+
{children}
+
+ );
};
diff --git a/apps/reactlit-examples/src/components/main.tsx b/apps/reactlit-examples/src/components/main.tsx
index bcd6cb0..9eda187 100644
--- a/apps/reactlit-examples/src/components/main.tsx
+++ b/apps/reactlit-examples/src/components/main.tsx
@@ -1,78 +1,78 @@
-import { Box, Container, Flex, Text } from '@radix-ui/themes';
-import { Geist, Geist_Mono } from 'next/font/google';
-import Head from 'next/head';
-import React from 'react';
-import { DebugToggle } from './debug-toggle';
-import { Menu } from './menu';
-import { ThemeToggle } from './theme-toggle';
+import { Box, Container, Flex, Text } from "@radix-ui/themes";
+import { Geist, Geist_Mono } from "next/font/google";
+import Head from "next/head";
+import React from "react";
+import { DebugToggle } from "./debug-toggle";
+import { Menu } from "./menu";
+import { ThemeToggle } from "./theme-toggle";
const geistSans = Geist({
- variable: '--font-geist-sans',
- subsets: ['latin'],
+ variable: "--font-geist-sans",
+ subsets: ["latin"],
});
const geistMono = Geist_Mono({
- variable: '--font-geist-mono',
- subsets: ['latin'],
+ variable: "--font-geist-mono",
+ subsets: ["latin"],
});
export function Main({
- title,
- children,
+ title,
+ children,
}: {
- title: string;
- children: React.ReactNode;
+ title: string;
+ children: React.ReactNode;
}) {
- return (
-
-
-
- {title}
-
-
-
-
-
- {title}
-
-
-
-
-
-
-
-
-
-
-
-
- {children}
-
-
-
-
-
-
-
-
- );
+ return (
+
+
+
+ {title}
+
+
+
+
+
+ {title}
+
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+
+
+ );
}
diff --git a/apps/reactlit-examples/src/components/menu.tsx b/apps/reactlit-examples/src/components/menu.tsx
index 682d58c..88f56f3 100644
--- a/apps/reactlit-examples/src/components/menu.tsx
+++ b/apps/reactlit-examples/src/components/menu.tsx
@@ -1,29 +1,29 @@
-import { Box, Flex, Link } from '@radix-ui/themes';
-import NextLink from 'next/link';
-import { usePathname } from 'next/navigation';
-import { ReactNode } from 'react';
+import { Box, Flex, Link } from "@radix-ui/themes";
+import NextLink from "next/link";
+import { usePathname } from "next/navigation";
+import { ReactNode } from "react";
function MenuItem({ href, children }: { href: string; children: ReactNode }) {
- const pathname = usePathname();
- const isActive = pathname === href;
- return (
-
-
- {children}
-
-
- );
+ const pathname = usePathname();
+ const isActive = pathname === href;
+ return (
+
+
+ {children}
+
+
+ );
}
export function Menu() {
- return (
-
-
-
-
-
-
-
-
- );
+ return (
+
+
+
+
+
+
+
+
+ );
}
diff --git a/apps/reactlit-examples/src/components/theme-toggle.tsx b/apps/reactlit-examples/src/components/theme-toggle.tsx
index 4a09854..089ec39 100644
--- a/apps/reactlit-examples/src/components/theme-toggle.tsx
+++ b/apps/reactlit-examples/src/components/theme-toggle.tsx
@@ -1,30 +1,30 @@
-'use client';
-import { useEffect, useState } from 'react';
-import { Button, Tooltip } from '@radix-ui/themes';
-import { MoonIcon, SunIcon } from 'lucide-react';
-import { useTheme } from 'next-themes';
+"use client";
+import { Button, Tooltip } from "@radix-ui/themes";
+import { MoonIcon, SunIcon } from "lucide-react";
+import { useTheme } from "next-themes";
+import { useEffect, useState } from "react";
export function ThemeToggle() {
- const { resolvedTheme, setTheme } = useTheme();
- const [mounted, setMounted] = useState(false);
+ const { resolvedTheme, setTheme } = useTheme();
+ const [mounted, setMounted] = useState(false);
- useEffect(() => {
- setMounted(true);
- }, []);
+ useEffect(() => {
+ setMounted(true);
+ }, []);
- if (!mounted) {
- return null;
- }
+ if (!mounted) {
+ return null;
+ }
- return (
-
-
-
- );
+ return (
+
+
+
+ );
}
diff --git a/apps/reactlit-examples/src/mocks/todos.ts b/apps/reactlit-examples/src/mocks/todos.ts
index 28d29b6..9a0133f 100644
--- a/apps/reactlit-examples/src/mocks/todos.ts
+++ b/apps/reactlit-examples/src/mocks/todos.ts
@@ -1,49 +1,52 @@
// This is mocking a backend API for demo purposes
-import { wait } from '../utils/wait';
+import { wait } from "../utils/wait";
export type Todo = {
- id: string;
- task: string;
- completed: boolean;
+ id: string;
+ task: string;
+ completed: boolean;
};
// we add a delay to these to simulate a network request
export class TodoService {
- constructor(private todos: Todo[], private readonly delay: number = 0) {}
+ constructor(
+ private todos: Todo[],
+ private readonly delay: number = 0,
+ ) {}
- async getTodos(fail?: boolean) {
- console.log('getTodos');
- await wait(this.delay);
- if (fail) {
- throw new Error('purposefully failed');
- }
- return this.todos;
- }
+ async getTodos(fail?: boolean) {
+ console.log("getTodos");
+ await wait(this.delay);
+ if (fail) {
+ throw new Error("purposefully failed");
+ }
+ return this.todos;
+ }
- async addTodo(todo?: Partial>) {
- await wait(this.delay);
- const newTodo = {
- task: `New Todo ${this.todos.length + 1}`,
- completed: false,
- ...(todo ?? {}),
- id: `todo-${this.todos.length + 1}`,
- };
- this.todos = [...this.todos, newTodo];
- return newTodo;
- }
+ async addTodo(todo?: Partial>) {
+ await wait(this.delay);
+ const newTodo = {
+ task: `New Todo ${this.todos.length + 1}`,
+ completed: false,
+ ...(todo ?? {}),
+ id: `todo-${this.todos.length + 1}`,
+ };
+ this.todos = [...this.todos, newTodo];
+ return newTodo;
+ }
- async updateTodo(id: string, todo: Partial) {
- await wait(this.delay);
- const index = this.todos.findIndex((t) => t.id === id);
- if (index === -1) {
- throw new Error(`Todo not found: ${id}`);
- }
- this.todos = [
- ...this.todos.slice(0, index),
- { ...this.todos[index], ...todo },
- ...this.todos.slice(index + 1),
- ];
- return this.todos[index];
- }
+ async updateTodo(id: string, todo: Partial) {
+ await wait(this.delay);
+ const index = this.todos.findIndex((t) => t.id === id);
+ if (index === -1) {
+ throw new Error(`Todo not found: ${id}`);
+ }
+ this.todos = [
+ ...this.todos.slice(0, index),
+ { ...this.todos[index], ...todo },
+ ...this.todos.slice(index + 1),
+ ];
+ return this.todos[index];
+ }
}
diff --git a/apps/reactlit-examples/src/pages/_app.tsx b/apps/reactlit-examples/src/pages/_app.tsx
index 7aa93eb..3cbf9f5 100644
--- a/apps/reactlit-examples/src/pages/_app.tsx
+++ b/apps/reactlit-examples/src/pages/_app.tsx
@@ -1,21 +1,21 @@
-import '@/styles/globals.css';
-import '@radix-ui/themes/styles.css';
-import { Theme } from '@radix-ui/themes';
-import { ThemeProvider } from 'next-themes';
-import type { AppProps } from 'next/app';
-import { Main } from '@/components/main';
-import { DebugProvider } from '@/components/debug-toggle';
+import "@/styles/globals.css";
+import "@radix-ui/themes/styles.css";
+import { DebugProvider } from "@/components/debug-toggle";
+import { Main } from "@/components/main";
+import { Theme } from "@radix-ui/themes";
+import { ThemeProvider } from "next-themes";
+import type { AppProps } from "next/app";
export default function App({ Component, pageProps }: AppProps) {
- return (
-
-
-
-
-
-
-
-
-
- );
+ return (
+
+
+
+
+
+
+
+
+
+ );
}
diff --git a/apps/reactlit-examples/src/pages/_document.tsx b/apps/reactlit-examples/src/pages/_document.tsx
index 89a710b..7525c32 100644
--- a/apps/reactlit-examples/src/pages/_document.tsx
+++ b/apps/reactlit-examples/src/pages/_document.tsx
@@ -1,15 +1,15 @@
-import { Head, Html, Main, NextScript } from 'next/document';
+import { Head, Html, Main, NextScript } from "next/document";
export default function Document() {
- return (
-
-
-
-
-
-
-
-
-
- );
+ return (
+
+
+
+
+
+
+
+
+
+ );
}
diff --git a/apps/reactlit-examples/src/pages/hello-world-vanilla/index.tsx b/apps/reactlit-examples/src/pages/hello-world-vanilla/index.tsx
index 180bcfb..8dbe726 100644
--- a/apps/reactlit-examples/src/pages/hello-world-vanilla/index.tsx
+++ b/apps/reactlit-examples/src/pages/hello-world-vanilla/index.tsx
@@ -1,130 +1,130 @@
-import { Reactlit, useReactlitState } from '@reactlit/core';
-import { Inputs, Label } from '@reactlit/vanilla';
+import { Reactlit, useReactlitState } from "@reactlit/core";
+import { Inputs, Label } from "@reactlit/vanilla";
-const LabelProps = { className: 'flex items-center gap-2 mb-2' };
+const LabelProps = { className: "flex items-center gap-2 mb-2" };
export default function HelloWorldVanilla() {
- const [appState, setAppState] = useReactlitState({
- name: '',
- pickedNumbers: [],
- pickedColors: [],
- chosenNumber: '',
- chosenColor: '',
- });
+ const [appState, setAppState] = useReactlitState({
+ name: "",
+ pickedNumbers: [],
+ pickedColors: [],
+ chosenNumber: "",
+ chosenColor: "",
+ });
- return (
-
- {async ({ display, view }) => {
- display(Hello World Vanilla
);
+ return (
+
+ {async ({ display, view }) => {
+ display(Hello World Vanilla
);
- const name = view(
- 'name',
- Label('Name', LabelProps),
- Inputs.Text({
- id: 'name',
- className: 'border p-0.5',
- placeholder: 'Enter your name',
- })
- );
+ const name = view(
+ "name",
+ Label("Name", LabelProps),
+ Inputs.Text({
+ id: "name",
+ className: "border p-0.5",
+ placeholder: "Enter your name",
+ }),
+ );
- display(Hello {name}!
);
+ display(Hello {name}!
);
- const picked = view(
- 'pickedNumbers',
- Label('Pick any number', LabelProps),
- Inputs.Check(['One', 'Two', 'Three'], {
- className: {
- wrapper: 'flex gap-2',
- item: {
- input: 'border p-0.5 mr-1',
- },
- },
- })
- );
- display(Picked: {picked.join(', ')}!
);
+ const picked = view(
+ "pickedNumbers",
+ Label("Pick any number", LabelProps),
+ Inputs.Check(["One", "Two", "Three"], {
+ className: {
+ wrapper: "flex gap-2",
+ item: {
+ input: "border p-0.5 mr-1",
+ },
+ },
+ }),
+ );
+ display(Picked: {picked.join(", ")}!
);
- const pickedColors = view(
- 'pickedColors',
- Label('Pick any color', LabelProps),
- Inputs.Check(
- [
- { label: 'Red', value: '#FF0000' },
- { label: 'Green', value: '#00FF00' },
- { label: 'Blue', value: '#0000FF' },
- ],
- {
- className: {
- wrapper: 'flex gap-2',
- item: {
- input: 'border p-0.5 mr-1',
- },
- },
- valueof: (item) => item.value,
- format: (item) => (
-
- {item.label}
-
- ),
- disabled: (item) => item.value === '#FFFFFF',
- }
- )
- );
- display(Colors: {JSON.stringify(pickedColors)}!
);
+ const pickedColors = view(
+ "pickedColors",
+ Label("Pick any color", LabelProps),
+ Inputs.Check(
+ [
+ { label: "Red", value: "#FF0000" },
+ { label: "Green", value: "#00FF00" },
+ { label: "Blue", value: "#0000FF" },
+ ],
+ {
+ className: {
+ wrapper: "flex gap-2",
+ item: {
+ input: "border p-0.5 mr-1",
+ },
+ },
+ valueof: (item) => item.value,
+ format: (item) => (
+
+ {item.label}
+
+ ),
+ disabled: (item) => item.value === "#FFFFFF",
+ },
+ ),
+ );
+ display(Colors: {JSON.stringify(pickedColors)}!
);
- const chosenNumber = view(
- 'chosenNumber',
- Label('Choose a number', LabelProps),
- Inputs.Radio(['One', 'Two', 'Three'], {
- className: {
- wrapper: 'flex gap-2',
- item: {
- input: 'border p-0.5 mr-1',
- },
- },
- })
- );
- display(Chosen Number: {chosenNumber}!
);
+ const chosenNumber = view(
+ "chosenNumber",
+ Label("Choose a number", LabelProps),
+ Inputs.Radio(["One", "Two", "Three"], {
+ className: {
+ wrapper: "flex gap-2",
+ item: {
+ input: "border p-0.5 mr-1",
+ },
+ },
+ }),
+ );
+ display(Chosen Number: {chosenNumber}!
);
- const chosenColor = view(
- 'chosenColor',
- Label('Choose a color', LabelProps),
- Inputs.Radio(
- [
- { label: 'Red', value: '#FF0000' },
- { label: 'Green', value: '#00FF00' },
- { label: 'Blue', value: '#0000FF' },
- ],
- {
- className: {
- wrapper: 'flex gap-2',
- item: {
- input: 'border p-0.5 mr-1',
- },
- },
- valueof: (item) => item.value,
- format: (item) => (
-
- {item.label}
-
- ),
- disabled: (item) => item.value === '#FFFFFF',
- }
- )
- );
- display(Chosen Color: {JSON.stringify(chosenColor)}!
);
- }}
-
- );
+ const chosenColor = view(
+ "chosenColor",
+ Label("Choose a color", LabelProps),
+ Inputs.Radio(
+ [
+ { label: "Red", value: "#FF0000" },
+ { label: "Green", value: "#00FF00" },
+ { label: "Blue", value: "#0000FF" },
+ ],
+ {
+ className: {
+ wrapper: "flex gap-2",
+ item: {
+ input: "border p-0.5 mr-1",
+ },
+ },
+ valueof: (item) => item.value,
+ format: (item) => (
+
+ {item.label}
+
+ ),
+ disabled: (item) => item.value === "#FFFFFF",
+ },
+ ),
+ );
+ display(Chosen Color: {JSON.stringify(chosenColor)}!
);
+ }}
+
+ );
}
diff --git a/apps/reactlit-examples/src/pages/hello-world/index.tsx b/apps/reactlit-examples/src/pages/hello-world/index.tsx
index 2fa51d1..47c62f9 100644
--- a/apps/reactlit-examples/src/pages/hello-world/index.tsx
+++ b/apps/reactlit-examples/src/pages/hello-world/index.tsx
@@ -1,26 +1,26 @@
-import { Reactlit, useReactlitState } from '@reactlit/core';
-import { Inputs, Label } from '@reactlit/radix';
+import { Reactlit, useReactlitState } from "@reactlit/core";
+import { Inputs, Label } from "@reactlit/radix";
export default function HelloWorld() {
- const [appState, setAppState] = useReactlitState({
- name: '',
- });
- return (
-
- {async ({ display, view }) => {
- display(Hello World
);
- const name = view(
- 'name',
- Label('Name'),
- Inputs.Text({
- placeholder: 'Enter your name',
- })
- );
- if (!name) {
- throw new Error('Please enter your name');
- }
- display(Hello {name}!
);
- }}
-
- );
+ const [appState, setAppState] = useReactlitState({
+ name: "",
+ });
+ return (
+
+ {async ({ display, view }) => {
+ display(Hello World
);
+ const name = view(
+ "name",
+ Label("Name"),
+ Inputs.Text({
+ placeholder: "Enter your name",
+ }),
+ );
+ if (!name) {
+ throw new Error("Please enter your name");
+ }
+ display(Hello {name}!
);
+ }}
+
+ );
}
diff --git a/apps/reactlit-examples/src/pages/index.tsx b/apps/reactlit-examples/src/pages/index.tsx
index 0e0c26d..8ad6e22 100644
--- a/apps/reactlit-examples/src/pages/index.tsx
+++ b/apps/reactlit-examples/src/pages/index.tsx
@@ -1,12 +1,12 @@
-import { ArrowBigLeftDash } from 'lucide-react';
+import { ArrowBigLeftDash } from "lucide-react";
export default function Home() {
- return (
-
-
Welcome to ReactLit.
-
-
Explore the examples to get started.
-
-
- );
+ return (
+
+
Welcome to ReactLit.
+
+
Explore the examples to get started.
+
+
+ );
}
diff --git a/apps/reactlit-examples/src/pages/layout-test/index.tsx b/apps/reactlit-examples/src/pages/layout-test/index.tsx
index bbaf310..85718d8 100644
--- a/apps/reactlit-examples/src/pages/layout-test/index.tsx
+++ b/apps/reactlit-examples/src/pages/layout-test/index.tsx
@@ -1,49 +1,49 @@
-import { Debug, useDebug } from '@/components/debug-toggle';
+import { Debug, useDebug } from "@/components/debug-toggle";
import {
- DataFetchingPlugin,
- LayoutView,
- useReactlit,
- useReactlitState,
- defaultLayoutState,
-} from '@reactlit/core';
-import { Inputs, Label, RadixTheme } from '@reactlit/radix';
+ DataFetchingPlugin,
+ LayoutView,
+ defaultLayoutState,
+ useReactlit,
+ useReactlitState,
+} from "@reactlit/core";
+import { Inputs, Label, RadixTheme } from "@reactlit/radix";
export default function Starter() {
- const [appState, setAppState] = useReactlitState({
- layout1: defaultLayoutState(2),
- name: '',
- weight: 'regular',
- size: 1,
- });
- const Reactlit = useReactlit(DataFetchingPlugin);
- const debug = useDebug();
- return (
-
-
- {async (ctx) => {
- const { view } = ctx;
- const [col1, col2] = view(
- 'layout1',
- Debug,
- ,
- LayoutView(2, )
- );
- const v1 = col1.view(
- 'leftInput',
- Debug,
- Label('Column Left'),
- Inputs.Text()
- );
- col1.display(Debug, v1);
- const v2 = col2.view(
- 'rightInput',
- Debug,
- Label('Column Right'),
- Inputs.Text()
- );
- col2.display(Debug, v2);
- }}
-
-
- );
+ const [appState, setAppState] = useReactlitState({
+ layout1: defaultLayoutState(2),
+ name: "",
+ weight: "regular",
+ size: 1,
+ });
+ const Reactlit = useReactlit(DataFetchingPlugin);
+ const debug = useDebug();
+ return (
+
+
+ {async (ctx) => {
+ const { view } = ctx;
+ const [col1, col2] = view(
+ "layout1",
+ Debug,
+ ,
+ LayoutView(2, ),
+ );
+ const v1 = col1.view(
+ "leftInput",
+ Debug,
+ Label("Column Left"),
+ Inputs.Text(),
+ );
+ col1.display(Debug, v1);
+ const v2 = col2.view(
+ "rightInput",
+ Debug,
+ Label("Column Right"),
+ Inputs.Text(),
+ );
+ col2.display(Debug, v2);
+ }}
+
+
+ );
}
diff --git a/apps/reactlit-examples/src/pages/radix-inputs/index.tsx b/apps/reactlit-examples/src/pages/radix-inputs/index.tsx
index 2778412..8da4f1c 100644
--- a/apps/reactlit-examples/src/pages/radix-inputs/index.tsx
+++ b/apps/reactlit-examples/src/pages/radix-inputs/index.tsx
@@ -1,196 +1,200 @@
-import { Debug, useDebug } from '@/components/debug-toggle';
+import { Debug, useDebug } from "@/components/debug-toggle";
import {
- Badge,
- ChevronDownIcon,
- DataList,
- ThickChevronRightIcon,
-} from '@radix-ui/themes';
+ Badge,
+ ChevronDownIcon,
+ DataList,
+ ThickChevronRightIcon,
+} from "@radix-ui/themes";
import {
- defaultLayoutState,
- LayoutView,
- Reactlit,
- useReactlitState,
- Wrapper,
- WrapperComponent,
-} from '@reactlit/core';
-import { DefaultRadixWrapper, Inputs, Label } from '@reactlit/radix';
-import { useState } from 'react';
+ LayoutView,
+ Reactlit,
+ Wrapper,
+ WrapperComponent,
+ defaultLayoutState,
+ useReactlitState,
+} from "@reactlit/core";
+import { DefaultRadixWrapper, Inputs, Label } from "@reactlit/radix";
+import { useState } from "react";
interface Country {
- name: string;
- region: string;
- subregion: string;
- population: number;
- code: string;
+ name: string;
+ region: string;
+ subregion: string;
+ population: number;
+ code: string;
}
export async function fetchCountries(): Promise {
- const results = await fetch(
- 'https://restcountries.com/v3.1/all?fields=name,region,subregion,population,cca2',
- {
- cache: 'force-cache',
- next: {
- revalidate: 300,
- tags: ['countries'],
- },
- }
- );
- const rawResults = await results.json();
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- return rawResults.map((r: any) => ({
- name: r.name.common,
- region: r.region,
- subregion: r.subregion,
- population: r.population,
- code: r.cca2,
- }));
+ const results = await fetch(
+ "https://restcountries.com/v3.1/all?fields=name,region,subregion,population,cca2",
+ {
+ cache: "force-cache",
+ next: {
+ revalidate: 300,
+ tags: ["countries"],
+ },
+ },
+ );
+ const rawResults = await results.json();
+ // biome-ignore lint/suspicious/noExplicitAny:
+ return rawResults.map((r: any) => ({
+ name: r.name.common,
+ region: r.region,
+ subregion: r.subregion,
+ population: r.population,
+ code: r.cca2,
+ }));
}
const DisplayLabel = (label: string) => {
- const DisplayLabelComponent: WrapperComponent = ({ children }) => (
-
- {label}
- {children}
-
- );
- return DisplayLabelComponent;
+ const DisplayLabelComponent: WrapperComponent = ({ children }) => (
+
+ {label}
+ {children}
+
+ );
+ return DisplayLabelComponent;
};
const ResultsWrapper: Wrapper = ({ children }) => {
- const [open, setOpen] = useState(true);
- return (
- setOpen(!open)}
- >
-
- {open ? : }
- Results
-
-
- {children}
-
-
- );
+ const [open, setOpen] = useState(true);
+ return (
+ setOpen(!open)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter" || e.key === " ") {
+ setOpen(!open);
+ }
+ }}
+ >
+
+ {open ? : }
+ Results
+
+
+ {children}
+
+
+ );
};
export default function RadixInputs() {
- const [appState, setAppState] = useReactlitState({
- countrySearch: '',
- country: undefined as Country | undefined,
- results: defaultLayoutState(1),
- name: '',
- bio: '',
- number: [],
- letter: undefined as string | undefined,
- color: 'red',
- slider: 0,
- rangeSlider: [20, 80],
- enableLetter: true,
- });
- const debug = useDebug();
- return (
-
-
- {async ({ display, view }) => {
- display(Inputs test
);
- const [results] = view('results', ResultsWrapper, LayoutView(1));
- const displayResult = (label: string, value: React.ReactNode) => {
- results.display(Debug, DisplayLabel(label), value);
- };
- const name = view(
- 'name',
- Label('Name'),
- Inputs.Text({
- placeholder: 'Enter your name',
- })
- );
- displayResult('Name', name);
- const bio = view(
- 'bio',
- Label('Bio'),
- Inputs.TextArea({
- placeholder: 'Enter your bio',
- })
- );
- displayResult('Bio', bio);
- const number = view(
- 'number',
- Label('Pick any numbers'),
- Inputs.Check({ one: '1', two: '2', three: '3' })
- );
- displayResult('Numbers', number);
- if (
- view(
- 'enableLetter',
- Label('Letter Selection'),
- ,
- Inputs.Switch()
- )
- ) {
- const letter = view(
- 'letter',
- Label('Pick one Letter'),
- Inputs.Radio(['A', 'B', 'C'])
- );
- displayResult('Letter', letter);
- }
- const color = view(
- 'color',
- Label('Pick a color'),
- ,
- Inputs.Select(['red', 'blue', 'green'] as const)
- );
- displayResult('Color', {color});
- const slider = view(
- 'slider',
- Label('Slider'),
- Inputs.Slider({
- min: 0,
- max: 100,
- })
- );
- displayResult('Slider', slider);
- const rangeSlider = view(
- 'rangeSlider',
- Label('Range Slider'),
- Inputs.RangeSlider({
- min: 0,
- max: 100,
- })
- );
- displayResult('Range Slider', rangeSlider.join(' - '));
- const countries = await fetchCountries();
- display(Select a country
);
- const filteredCountries = view(
- 'countrySearch',
- Label('Search'),
- Inputs.Search(countries, {
- placeholder: 'Search countries...',
- })
- );
- const selectedCountry = view(
- 'country',
- Label('Countries'),
- Inputs.Table(filteredCountries, {
- getRowId: (country) => country.code,
- className: 'h-[300px]',
- })
- );
- displayResult(
- 'Country',
- <>
- {selectedCountry?.code ? (
-
- ) : (
- 'Select a country'
- )}
- >
- );
- }}
-
-
- );
+ const [appState, setAppState] = useReactlitState({
+ countrySearch: "",
+ country: undefined as Country | undefined,
+ results: defaultLayoutState(1),
+ name: "",
+ bio: "",
+ number: [],
+ letter: undefined as string | undefined,
+ color: "red",
+ slider: 0,
+ rangeSlider: [20, 80],
+ enableLetter: true,
+ });
+ const debug = useDebug();
+ return (
+
+
+ {async ({ display, view }) => {
+ display(Inputs test
);
+ const [results] = view("results", ResultsWrapper, LayoutView(1));
+ const displayResult = (label: string, value: React.ReactNode) => {
+ results.display(Debug, DisplayLabel(label), value);
+ };
+ const name = view(
+ "name",
+ Label("Name"),
+ Inputs.Text({
+ placeholder: "Enter your name",
+ }),
+ );
+ displayResult("Name", name);
+ const bio = view(
+ "bio",
+ Label("Bio"),
+ Inputs.TextArea({
+ placeholder: "Enter your bio",
+ }),
+ );
+ displayResult("Bio", bio);
+ const number = view(
+ "number",
+ Label("Pick any numbers"),
+ Inputs.Check({ one: "1", two: "2", three: "3" }),
+ );
+ displayResult("Numbers", number);
+ if (
+ view(
+ "enableLetter",
+ Label("Letter Selection"),
+ ,
+ Inputs.Switch(),
+ )
+ ) {
+ const letter = view(
+ "letter",
+ Label("Pick one Letter"),
+ Inputs.Radio(["A", "B", "C"]),
+ );
+ displayResult("Letter", letter);
+ }
+ const color = view(
+ "color",
+ Label("Pick a color"),
+ ,
+ Inputs.Select(["red", "blue", "green"] as const),
+ );
+ displayResult("Color", {color});
+ const slider = view(
+ "slider",
+ Label("Slider"),
+ Inputs.Slider({
+ min: 0,
+ max: 100,
+ }),
+ );
+ displayResult("Slider", slider);
+ const rangeSlider = view(
+ "rangeSlider",
+ Label("Range Slider"),
+ Inputs.RangeSlider({
+ min: 0,
+ max: 100,
+ }),
+ );
+ displayResult("Range Slider", rangeSlider.join(" - "));
+ const countries = await fetchCountries();
+ display(Select a country
);
+ const filteredCountries = view(
+ "countrySearch",
+ Label("Search"),
+ Inputs.Search(countries, {
+ placeholder: "Search countries...",
+ }),
+ );
+ const selectedCountry = view(
+ "country",
+ Label("Countries"),
+ Inputs.Table(filteredCountries, {
+ getRowId: (country) => country.code,
+ className: "h-[300px]",
+ }),
+ );
+ displayResult(
+ "Country",
+ selectedCountry?.code ? (
+
+ ) : (
+ "Select a country"
+ ),
+ );
+ }}
+
+
+ );
}
diff --git a/apps/reactlit-examples/src/pages/starter/index.tsx b/apps/reactlit-examples/src/pages/starter/index.tsx
index 67fcf6f..0fc015a 100644
--- a/apps/reactlit-examples/src/pages/starter/index.tsx
+++ b/apps/reactlit-examples/src/pages/starter/index.tsx
@@ -1,63 +1,63 @@
-import { Debug, useDebug } from '@/components/debug-toggle';
-import { Text } from '@radix-ui/themes';
-import { textPropDefs } from '@radix-ui/themes/props';
+import { Debug, useDebug } from "@/components/debug-toggle";
+import { Text } from "@radix-ui/themes";
+import { textPropDefs } from "@radix-ui/themes/props";
import {
- DataFetchingPlugin,
- useReactlit,
- useReactlitState,
-} from '@reactlit/core';
-import { DefaultRadixWrapper, Inputs, Label } from '@reactlit/radix';
+ DataFetchingPlugin,
+ useReactlit,
+ useReactlitState,
+} from "@reactlit/core";
+import { DefaultRadixWrapper, Inputs, Label } from "@reactlit/radix";
export default function Starter() {
- const [appState, setAppState] = useReactlitState({
- name: '',
- weight: 'regular',
- size: 1,
- });
- const Reactlit = useReactlit(DataFetchingPlugin);
- const debug = useDebug();
- return (
-
-
- {async (ctx) => {
- const { display, view } = ctx;
- const name = view(
- 'name',
- Debug,
- Label('What is your name?'),
- Inputs.Text({
- placeholder: 'Enter name',
- })
- );
- const weight = view(
- 'weight',
- Debug,
- Label('Weight'),
- Inputs.Radio(['light', 'regular', 'medium', 'bold'] as const)
- );
- const size = view(
- 'size',
- Debug,
- Label('Size'),
- Inputs.Slider({
- min: 1,
- max: 9,
- })
- );
+ const [appState, setAppState] = useReactlitState({
+ name: "",
+ weight: "regular",
+ size: 1,
+ });
+ const Reactlit = useReactlit(DataFetchingPlugin);
+ const debug = useDebug();
+ return (
+
+
+ {async (ctx) => {
+ const { display, view } = ctx;
+ const name = view(
+ "name",
+ Debug,
+ Label("What is your name?"),
+ Inputs.Text({
+ placeholder: "Enter name",
+ }),
+ );
+ const weight = view(
+ "weight",
+ Debug,
+ Label("Weight"),
+ Inputs.Radio(["light", "regular", "medium", "bold"] as const),
+ );
+ const size = view(
+ "size",
+ Debug,
+ Label("Size"),
+ Inputs.Slider({
+ min: 1,
+ max: 9,
+ }),
+ );
- display(
);
- display(
- Debug,
-
- Hello to {name ? name : Enter Name} from
- Reactlit!
-
- );
- }}
-
-
- );
+ display(
);
+ display(
+ Debug,
+
+ Hello to {name ? name : Enter Name} from
+ Reactlit!
+ ,
+ );
+ }}
+
+
+ );
}
diff --git a/apps/reactlit-examples/src/pages/todo-list/index.tsx b/apps/reactlit-examples/src/pages/todo-list/index.tsx
index de8d016..344ec53 100644
--- a/apps/reactlit-examples/src/pages/todo-list/index.tsx
+++ b/apps/reactlit-examples/src/pages/todo-list/index.tsx
@@ -1,101 +1,101 @@
-import { Button, Callout, Spinner } from '@radix-ui/themes';
-import { DataFetchingPlugin, useReactlit } from '@reactlit/core';
-import { DefaultRadixWrapper, Inputs, Label } from '@reactlit/radix';
-import { InfoIcon } from 'lucide-react';
-import { TodoService } from '../../mocks/todos';
+import { Button, Callout, Spinner } from "@radix-ui/themes";
+import { DataFetchingPlugin, useReactlit } from "@reactlit/core";
+import { DefaultRadixWrapper, Inputs, Label } from "@reactlit/radix";
+import { InfoIcon } from "lucide-react";
+import { TodoService } from "../../mocks/todos";
export const Loader = ({ message }: { message: string }) => {
- return (
-
- {message}
-
- );
+ return (
+
+ {message}
+
+ );
};
const api = new TodoService([], 1000);
export default function TodoList() {
- const Reactlit = useReactlit(DataFetchingPlugin);
- return (
-
-
- {async ({ display, view, set, changed, fetcher }) => {
- display(
-
-
-
-
-
- This app is purposely slow to show how Reactlit handles loading
- states.
-
-
- );
- const todosFetcher = fetcher(['todos'], () => api.getTodos());
- display(
-
-
-
- );
- view(
- 'adding',
- Inputs.AsyncButton(
- async () => {
- const newTodo = await api.addTodo();
- await todosFetcher.refetch();
- set('selectedTodo', newTodo.id);
- },
- {
- disabled: todosFetcher.isFetching(),
- content: 'Add Todo',
- }
- )
- );
- const todos = todosFetcher.get() ?? [];
- const selectedTodo = view(
- 'selectedTodo',
- Inputs.Table(todos, {
- getRowId: (todo) => todo.id,
- columns: ['task', 'completed'],
- format: {
- completed: (completed) => (completed ? '☑️' : ''),
- },
- })
- );
- if (selectedTodo) {
- if (changed('selectedTodo')) {
- set('task', selectedTodo.task);
- set('completed', selectedTodo.completed);
- }
- const task = view('task', Label('Task'), Inputs.Text());
- const completed = view(
- 'completed',
- Label('Completed'),
- Inputs.Switch()
- );
- view(
- 'updaing',
- Inputs.AsyncButton(
- async () => {
- // todosFetcher.update((todos) => {
- // return todos.map((todo) =>
- // todo.id === selectedTodo.id
- // ? { ...todo, task, completed }
- // : todo
- // );
- // });
- await api.updateTodo(selectedTodo.id, { task, completed });
- await todosFetcher.refetch();
- },
- {
- disabled: todosFetcher.isFetching(),
- content: 'Update',
- }
- )
- );
- }
- }}
-
-
- );
+ const Reactlit = useReactlit(DataFetchingPlugin);
+ return (
+
+
+ {async ({ display, view, set, changed, fetcher }) => {
+ display(
+
+
+
+
+
+ This app is purposely slow to show how Reactlit handles loading
+ states.
+
+ ,
+ );
+ const todosFetcher = fetcher(["todos"], () => api.getTodos());
+ display(
+
+
+
,
+ );
+ view(
+ "adding",
+ Inputs.AsyncButton(
+ async () => {
+ const newTodo = await api.addTodo();
+ await todosFetcher.refetch();
+ set("selectedTodo", newTodo.id);
+ },
+ {
+ disabled: todosFetcher.isFetching(),
+ content: "Add Todo",
+ },
+ ),
+ );
+ const todos = todosFetcher.get() ?? [];
+ const selectedTodo = view(
+ "selectedTodo",
+ Inputs.Table(todos, {
+ getRowId: (todo) => todo.id,
+ columns: ["task", "completed"],
+ format: {
+ completed: (completed) => (completed ? "☑️" : ""),
+ },
+ }),
+ );
+ if (selectedTodo) {
+ if (changed("selectedTodo")) {
+ set("task", selectedTodo.task);
+ set("completed", selectedTodo.completed);
+ }
+ const task = view("task", Label("Task"), Inputs.Text());
+ const completed = view(
+ "completed",
+ Label("Completed"),
+ Inputs.Switch(),
+ );
+ view(
+ "updaing",
+ Inputs.AsyncButton(
+ async () => {
+ // todosFetcher.update((todos) => {
+ // return todos.map((todo) =>
+ // todo.id === selectedTodo.id
+ // ? { ...todo, task, completed }
+ // : todo
+ // );
+ // });
+ await api.updateTodo(selectedTodo.id, { task, completed });
+ await todosFetcher.refetch();
+ },
+ {
+ disabled: todosFetcher.isFetching(),
+ content: "Update",
+ },
+ ),
+ );
+ }
+ }}
+
+
+ );
}
diff --git a/apps/reactlit-examples/src/styles/globals.css b/apps/reactlit-examples/src/styles/globals.css
index cb50ce9..3471086 100644
--- a/apps/reactlit-examples/src/styles/globals.css
+++ b/apps/reactlit-examples/src/styles/globals.css
@@ -3,56 +3,56 @@
@tailwind utilities;
:root {
- --background: #ffffff;
- --foreground: #171717;
+ --background: #ffffff;
+ --foreground: #171717;
}
@media (prefers-color-scheme: dark) {
- :root {
- --background: #0a0a0a;
- --foreground: #ededed;
- }
+ :root {
+ --background: #0a0a0a;
+ --foreground: #ededed;
+ }
}
body {
- color: var(--foreground);
- background: var(--background);
- font-family: Arial, Helvetica, sans-serif;
+ color: var(--foreground);
+ background: var(--background);
+ font-family: Arial, Helvetica, sans-serif;
}
tr.rt-TableRow:nth-child(even) {
- background-color: var(--gray-2);
+ background-color: var(--gray-2);
}
.overlay {
- position: fixed;
- cursor: pointer;
- top: 4rem;
- right: 2rem;
- padding: 1rem;
- background: var(--gray-2);
- border: 1px solid #bbb;
- border-radius: 0.5rem;
- opacity: 0.8;
- transition: all 0.2s;
- z-index: 100;
- width: 25%;
- overflow-x: hidden;
- overflow-y: hidden;
- max-height: 3.5rem;
+ position: fixed;
+ cursor: pointer;
+ top: 4rem;
+ right: 2rem;
+ padding: 1rem;
+ background: var(--gray-2);
+ border: 1px solid #bbb;
+ border-radius: 0.5rem;
+ opacity: 0.8;
+ transition: all 0.2s;
+ z-index: 100;
+ width: 25%;
+ overflow-x: hidden;
+ overflow-y: hidden;
+ max-height: 3.5rem;
}
.overlay.open {
- max-height: 80%;
+ max-height: 80%;
}
.overlay-header {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- margin-bottom: 1rem;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ margin-bottom: 1rem;
}
.overlay:hover {
- opacity: 1;
+ opacity: 1;
}
diff --git a/apps/reactlit-examples/src/utils/utils.ts b/apps/reactlit-examples/src/utils/utils.ts
index 9ad0df4..ac680b3 100644
--- a/apps/reactlit-examples/src/utils/utils.ts
+++ b/apps/reactlit-examples/src/utils/utils.ts
@@ -1,6 +1,6 @@
-import { type ClassValue, clsx } from 'clsx';
-import { twMerge } from 'tailwind-merge';
+import { type ClassValue, clsx } from "clsx";
+import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
- return twMerge(clsx(inputs));
+ return twMerge(clsx(inputs));
}
diff --git a/apps/reactlit-examples/src/utils/wait.ts b/apps/reactlit-examples/src/utils/wait.ts
index cbd907e..76d5de3 100644
--- a/apps/reactlit-examples/src/utils/wait.ts
+++ b/apps/reactlit-examples/src/utils/wait.ts
@@ -1,3 +1,3 @@
export async function wait(ms: number) {
- return new Promise((resolve) => setTimeout(resolve, ms));
+ return new Promise((resolve) => setTimeout(resolve, ms));
}
diff --git a/apps/reactlit-examples/tailwind.config.ts b/apps/reactlit-examples/tailwind.config.ts
index 109807b..5d3c1bd 100644
--- a/apps/reactlit-examples/tailwind.config.ts
+++ b/apps/reactlit-examples/tailwind.config.ts
@@ -1,18 +1,18 @@
import type { Config } from "tailwindcss";
export default {
- content: [
- "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
- "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
- "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
- ],
- theme: {
- extend: {
- colors: {
- background: "var(--background)",
- foreground: "var(--foreground)",
- },
- },
- },
- plugins: [],
+ content: [
+ "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
+ "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
+ "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
+ ],
+ theme: {
+ extend: {
+ colors: {
+ background: "var(--background)",
+ foreground: "var(--foreground)",
+ },
+ },
+ },
+ plugins: [],
} satisfies Config;
diff --git a/apps/reactlit-examples/tsconfig.json b/apps/reactlit-examples/tsconfig.json
index 572b7ad..9be29a0 100644
--- a/apps/reactlit-examples/tsconfig.json
+++ b/apps/reactlit-examples/tsconfig.json
@@ -1,22 +1,22 @@
{
- "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,
- "paths": {
- "@/*": ["./src/*"]
- }
- },
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
- "exclude": ["node_modules"]
+ "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,
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
+ "exclude": ["node_modules"]
}
diff --git a/biome.json b/biome.json
new file mode 100644
index 0000000..0541bd3
--- /dev/null
+++ b/biome.json
@@ -0,0 +1,43 @@
+{
+ "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
+ "vcs": {
+ "enabled": true,
+ "clientKind": "git",
+ "useIgnoreFile": true
+ },
+ "files": {
+ "ignoreUnknown": false,
+ "ignore": [
+ "node_modules",
+ "dist",
+ "out",
+ ".turbo",
+ ".next",
+ "public",
+ ".astro"
+ ]
+ },
+ "formatter": {
+ "enabled": true,
+ "indentStyle": "tab"
+ },
+ "organizeImports": {
+ "enabled": true
+ },
+ "linter": {
+ "enabled": true,
+ "rules": {
+ "style": {
+ "useImportType": "off"
+ }
+ }
+ },
+ "javascript": {
+ "formatter": {
+ "quoteStyle": "double"
+ },
+ "parser": {
+ "unsafeParameterDecoratorsEnabled": true
+ }
+ }
+}
diff --git a/justfile b/justfile
deleted file mode 100644
index e603642..0000000
--- a/justfile
+++ /dev/null
@@ -1,11 +0,0 @@
-build:
- pnpm run build
-
-rebuild:
- turbo run clean && pnpm i && turbo run build --force
-
-dev:
- pnpm run dev
-
-devdocs:
- pnpm run dev:docs
diff --git a/libs/core/.eslintrc.cjs b/libs/core/.eslintrc.cjs
index cf8b644..c4d733b 100644
--- a/libs/core/.eslintrc.cjs
+++ b/libs/core/.eslintrc.cjs
@@ -1,10 +1,10 @@
module.exports = {
- extends: ['next'],
- plugins: ['prettier'],
- rules: {
- 'no-console': 'error',
- 'prettier/prettier': 'warn',
- '@next/next/no-html-link-for-pages': 'off',
- 'react/jsx-key': 1,
- },
+ extends: ["next"],
+ plugins: ["prettier"],
+ rules: {
+ "no-console": "error",
+ "prettier/prettier": "warn",
+ "@next/next/no-html-link-for-pages": "off",
+ "react/jsx-key": 1,
+ },
};
diff --git a/libs/core/jest.config.cjs b/libs/core/jest.config.cjs
deleted file mode 100644
index 4796edd..0000000
--- a/libs/core/jest.config.cjs
+++ /dev/null
@@ -1,15 +0,0 @@
-/** @type {import('ts-jest').JestConfigWithTsJest} **/
-module.exports = {
- transform: {
- '^.+.tsx?$': ['ts-jest', {}],
- },
- roots: [''],
- moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
- testEnvironment: 'jsdom',
- modulePathIgnorePatterns: [
- '/test/__fixtures__',
- '/node_modules',
- '/dist',
- ],
- preset: 'ts-jest',
-};
diff --git a/libs/core/package.json b/libs/core/package.json
index 06635cd..28d6e88 100644
--- a/libs/core/package.json
+++ b/libs/core/package.json
@@ -1,67 +1,57 @@
{
- "name": "@reactlit/core",
- "description": "Simple micro-app framework for React",
- "version": "0.2.1",
- "license": "MIT",
- "homepage": "https://github.com/mshafir/reactlit",
- "repository": "github:mshafir/reactlit",
- "author": "Michael Shafir ",
- "type": "module",
- "main": "./dist/index.js",
- "types": "./dist/index.d.ts",
- "exports": {
- ".": {
- "types": "./dist/index.d.ts",
- "default": "./dist/index.js"
- }
- },
- "files": [
- "dist"
- ],
- "publishConfig": {
- "access": "public",
- "registry": "https://registry.npmjs.org/"
- },
- "scripts": {
- "prebuild": "cp ../../README.md .",
- "build": "tsup && tsc --project tsconfig.build.json",
- "dev": "tsup --dts --watch",
- "test": "jest",
- "clean": "rm -rf node_modules && rm -rf .turbo && rm -rf dist",
- "lint": "eslint src/**/*.ts* --fix"
- },
- "dependencies": {
- "@tanstack/react-query": "^5.62.3",
- "fast-deep-equal": "^3.1.3",
- "react-error-boundary": "^4.1.2",
- "zustand": "^5.0.3"
- },
- "devDependencies": {
- "@mollycule/vigilante": "^1.0.2",
- "@testing-library/dom": "^10.4.0",
- "@testing-library/jest-dom": "^6.6.3",
- "@testing-library/react": "^16.1.0",
- "@testing-library/user-event": "^14.5.2",
- "@types/jest": "^29.5.14",
- "@types/react": "^19.0.1",
- "@types/react-dom": "^19.0.2",
- "eslint": "^7.23.0",
- "eslint-config-next": "^12.0.8",
- "eslint-plugin-jest": "^28.9.0",
- "eslint-plugin-prettier": "^4.2.1",
- "eslint-plugin-react": "7.28.0",
- "jest": "^29.7.0",
- "jest-environment-jsdom": "^29.7.0",
- "prettier": "^2.8.0",
- "react": "^19.0.0",
- "react-dom": "^19.0.0",
- "ts-jest": "^29.2.5",
- "ts-node": "^10.9.2",
- "tsup": "^8.3.5",
- "typescript": "^5.7.2"
- },
- "peerDependencies": {
- "react": ">=17 <=19",
- "react-dom": ">=17 <=19"
- }
-}
\ No newline at end of file
+ "name": "@reactlit/core",
+ "description": "Simple micro-app framework for React",
+ "version": "0.2.1",
+ "license": "MIT",
+ "homepage": "https://github.com/mshafir/reactlit",
+ "repository": "github:mshafir/reactlit",
+ "author": "Michael Shafir ",
+ "type": "module",
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "default": "./dist/index.js"
+ }
+ },
+ "files": ["dist"],
+ "publishConfig": {
+ "access": "public",
+ "registry": "https://registry.npmjs.org/"
+ },
+ "scripts": {
+ "prebuild": "cp ../../README.md .",
+ "build": "tsup && tsc --project tsconfig.build.json",
+ "dev": "tsup --dts --watch",
+ "test": "vitest run",
+ "clean": "rm -rf node_modules && rm -rf .turbo && rm -rf dist",
+ "lint": "biome check --write"
+ },
+ "dependencies": {
+ "@tanstack/react-query": "^5.62.3",
+ "fast-deep-equal": "^3.1.3",
+ "react-error-boundary": "^4.1.2",
+ "zustand": "^5.0.3"
+ },
+ "devDependencies": {
+ "@mollycule/vigilante": "^1.0.2",
+ "@testing-library/dom": "^10.4.0",
+ "@testing-library/jest-dom": "^6.6.3",
+ "@testing-library/react": "^16.1.0",
+ "@testing-library/user-event": "^14.5.2",
+ "@types/react": "^19.0.1",
+ "@types/react-dom": "^19.0.2",
+ "@biomejs/biome": "1.9.4",
+ "jsdom": "^26.1.0",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "tsup": "^8.3.5",
+ "typescript": "*",
+ "vitest": "^3.1.2"
+ },
+ "peerDependencies": {
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ }
+}
diff --git a/libs/core/src/builtins/changed.ts b/libs/core/src/builtins/changed.ts
index ca1c324..513f537 100644
--- a/libs/core/src/builtins/changed.ts
+++ b/libs/core/src/builtins/changed.ts
@@ -1,48 +1,48 @@
-import { useCallback } from 'react';
-import { ReactlitContext, StateBase } from './types';
-import { InternalReactlitState } from './internal-state';
-import { deepEqual } from '../utils/deep-equal';
+import { useCallback } from "react";
+import { deepEqual } from "../utils/deep-equal";
+import { InternalReactlitState } from "./internal-state";
+import { ReactlitContext, StateBase } from "./types";
function deltas(
- state: T,
- previousState: T | undefined
+ state: T,
+ previousState: T | undefined,
): (keyof T)[] {
- return Object.keys(state).filter(
- (key: string) =>
- !previousState || !deepEqual(state[key], previousState[key])
- );
+ return Object.keys(state).filter(
+ (key: string) =>
+ !previousState || !deepEqual(state[key], previousState[key]),
+ );
}
export function useReactlitChanged(
- { state, previousState, setPreviousState }: InternalReactlitState,
- debug: boolean
+ { state, previousState, setPreviousState }: InternalReactlitState,
+ debug: boolean,
) {
- return useCallback['changed']>(
- (...keys) => {
- const changedKeys = deltas(state, previousState);
- const selectedChangedKeys = keys.filter((k) =>
- changedKeys.includes(k as string)
- );
- const isChanged = selectedChangedKeys.length > 0;
- if (isChanged) {
- if (debug) {
- for (const k of selectedChangedKeys) {
- // eslint-disable-next-line no-console
- console.debug(
- `changed ${String(k)}: ${previousState?.[k]} -> ${state[k]}`
- );
- }
- }
- setPreviousState((prev) => {
- let newState = prev;
- for (const k of selectedChangedKeys) {
- newState = { ...newState, [k]: state[k] };
- }
- return newState;
- });
- }
- return isChanged;
- },
- [state, previousState, setPreviousState, debug]
- );
+ return useCallback["changed"]>(
+ (...keys) => {
+ const changedKeys = deltas(state, previousState);
+ const selectedChangedKeys = keys.filter((k) =>
+ changedKeys.includes(k as string),
+ );
+ const isChanged = selectedChangedKeys.length > 0;
+ if (isChanged) {
+ if (debug) {
+ for (const k of selectedChangedKeys) {
+ // eslint-disable-next-line no-console
+ console.debug(
+ `changed ${String(k)}: ${previousState?.[k]} -> ${state[k]}`,
+ );
+ }
+ }
+ setPreviousState((prev) => {
+ let newState = prev;
+ for (const k of selectedChangedKeys) {
+ newState = { ...newState, [k]: state[k] };
+ }
+ return newState;
+ });
+ }
+ return isChanged;
+ },
+ [state, previousState, setPreviousState, debug],
+ );
}
diff --git a/libs/core/src/builtins/display.tsx b/libs/core/src/builtins/display.tsx
index df7a660..cc819fb 100644
--- a/libs/core/src/builtins/display.tsx
+++ b/libs/core/src/builtins/display.tsx
@@ -1,12 +1,12 @@
-import { ReactNode, useCallback, useState } from 'react';
-import { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary';
-import { tail } from '../utils/tail';
-import { ApplyWrappers, Wrapper } from '../wrappers';
-import { ReactlitContext, ReactlitProps, StateBase } from './types';
+import { ReactNode, useCallback, useState } from "react";
+import { ErrorBoundary as ReactErrorBoundary } from "react-error-boundary";
+import { tail } from "../utils/tail";
+import { ApplyWrappers, Wrapper } from "../wrappers";
+import { ReactlitContext, ReactlitProps, StateBase } from "./types";
interface DisplayState {
- position: number;
- elements: [string, React.ReactNode][];
+ position: number;
+ elements: [string, React.ReactNode][];
}
type KeyedDisplayArgs = [string, ...Wrapper[], ReactNode];
@@ -15,105 +15,105 @@ type UnkeyedDisplayArgs = [...Wrapper[], ReactNode];
export type DisplayArgs = KeyedDisplayArgs | UnkeyedDisplayArgs;
export function isKeyedDisplayArgs(
- args: DisplayArgs
+ args: DisplayArgs,
): args is KeyedDisplayArgs {
- return args.length > 1 && typeof args[0] === 'string';
+ return args.length > 1 && typeof args[0] === "string";
}
export function normalizeDisplayArgs(args: DisplayArgs) {
- const manualKey = isKeyedDisplayArgs(args) ? args[0] : undefined;
- const restArgs = isKeyedDisplayArgs(args)
- ? (args.slice(1) as UnkeyedDisplayArgs)
- : args;
- const [wrappers, node] = tail(restArgs);
- return {
- manualKey,
- wrappers,
- node,
- };
+ const manualKey = isKeyedDisplayArgs(args) ? args[0] : undefined;
+ const restArgs = isKeyedDisplayArgs(args)
+ ? (args.slice(1) as UnkeyedDisplayArgs)
+ : args;
+ const [wrappers, node] = tail(restArgs);
+ return {
+ manualKey,
+ wrappers,
+ node,
+ };
}
export function useReactlitDisplay({
- renderError,
-}: Pick, 'renderError'>) {
- const [renderState, setRenderState] = useState({
- position: 0,
- elements: [],
- });
+ renderError,
+}: Pick, "renderError">) {
+ const [renderState, setRenderState] = useState({
+ position: 0,
+ elements: [],
+ });
- const display = useCallback['display']>(
- (...args: DisplayArgs) => {
- const { manualKey, wrappers, node } = normalizeDisplayArgs(args);
+ const display = useCallback["display"]>(
+ (...args: DisplayArgs) => {
+ const { manualKey, wrappers, node } = normalizeDisplayArgs(args);
- setRenderState(({ position, elements }) => {
- const key = manualKey ?? `${position}`;
- const keyIndex = elements
- .slice(0, position)
- .findIndex(([k]) => manualKey && k === manualKey);
+ setRenderState(({ position, elements }) => {
+ const key = manualKey ?? `${position}`;
+ const keyIndex = elements
+ .slice(0, position)
+ .findIndex(([k]) => manualKey && k === manualKey);
- const element = (
-
-
- {node}
-
-
- );
- const newEntry = [key, element] as [string, React.ReactNode];
+ const element = (
+
+
+ {node}
+
+
+ );
+ const newEntry = [key, element] as [string, React.ReactNode];
- if (keyIndex !== -1) {
- return {
- position,
- elements: [
- ...elements.slice(0, keyIndex),
- newEntry,
- ...elements.slice(keyIndex + 1),
- ],
- };
- } else if (position < elements.length) {
- return {
- position: position + 1,
- elements: [
- ...elements.slice(0, position),
- newEntry,
- // for manual keys that haven't been found by the above case
- // we don't want to overwrite the index element because
- // it's likely a different element
- ...elements
- .slice(position + (manualKey ? 0 : 1))
- .filter((e) => !manualKey || e[0] !== manualKey),
- ],
- };
- } else {
- return {
- position: elements.length + 1,
- elements: [...elements, newEntry],
- };
- }
- });
- },
- [setRenderState, renderError]
- );
+ if (keyIndex !== -1) {
+ return {
+ position,
+ elements: [
+ ...elements.slice(0, keyIndex),
+ newEntry,
+ ...elements.slice(keyIndex + 1),
+ ],
+ };
+ }
+ if (position < elements.length) {
+ return {
+ position: position + 1,
+ elements: [
+ ...elements.slice(0, position),
+ newEntry,
+ // for manual keys that haven't been found by the above case
+ // we don't want to overwrite the index element because
+ // it's likely a different element
+ ...elements
+ .slice(position + (manualKey ? 0 : 1))
+ .filter((e) => !manualKey || e[0] !== manualKey),
+ ],
+ };
+ }
+ return {
+ position: elements.length + 1,
+ elements: [...elements, newEntry],
+ };
+ });
+ },
+ [renderError],
+ );
- const resetRenderPosition = useCallback(() => {
- setRenderState(({ elements }) => ({ elements, position: 0 }));
- }, [setRenderState]);
+ const resetRenderPosition = useCallback(() => {
+ setRenderState(({ elements }) => ({ elements, position: 0 }));
+ }, []);
- // truncates stranded elements after the last position
- const finalizeRender = useCallback(() => {
- setRenderState(({ elements, position }) => ({
- position,
- elements: elements.slice(0, position),
- }));
- }, [setRenderState]);
+ // truncates stranded elements after the last position
+ const finalizeRender = useCallback(() => {
+ setRenderState(({ elements, position }) => ({
+ position,
+ elements: elements.slice(0, position),
+ }));
+ }, []);
- return {
- renderState,
- display,
- resetRenderPosition,
- finalizeRender,
- };
+ return {
+ renderState,
+ display,
+ resetRenderPosition,
+ finalizeRender,
+ };
}
diff --git a/libs/core/src/builtins/internal-state.ts b/libs/core/src/builtins/internal-state.ts
index 6465651..a69ccd1 100644
--- a/libs/core/src/builtins/internal-state.ts
+++ b/libs/core/src/builtins/internal-state.ts
@@ -1,25 +1,25 @@
-import { useState } from 'react';
-import { useDeepMemo } from '../hooks/use-deep-memo';
-import { ReactlitStateSetter, StateBase } from './types';
+import { useState } from "react";
+import { useDeepMemo } from "../hooks/use-deep-memo";
+import { ReactlitStateSetter, StateBase } from "./types";
export interface InternalReactlitState {
- state: T;
- setState: ReactlitStateSetter;
- previousState: T;
- setPreviousState: React.Dispatch>;
+ state: T;
+ setState: ReactlitStateSetter;
+ previousState: T;
+ setPreviousState: React.Dispatch>;
}
export function useInternalReactlitState(
- rawState: T,
- setState: ReactlitStateSetter
+ rawState: T,
+ setState: ReactlitStateSetter,
): InternalReactlitState {
- const state = useDeepMemo(() => rawState, [rawState]);
- const [previousState, setPreviousState] = useState(state);
+ const state = useDeepMemo(() => rawState, [rawState]);
+ const [previousState, setPreviousState] = useState(state);
- return {
- state,
- setState,
- previousState,
- setPreviousState,
- };
+ return {
+ state,
+ setState,
+ previousState,
+ setPreviousState,
+ };
}
diff --git a/libs/core/src/builtins/set.ts b/libs/core/src/builtins/set.ts
index 8c4a192..1f01dc6 100644
--- a/libs/core/src/builtins/set.ts
+++ b/libs/core/src/builtins/set.ts
@@ -1,22 +1,22 @@
-import { useCallback } from 'react';
-import { deepEqual } from '../utils/deep-equal';
-import { ReactlitContext, StateBase } from './types';
-import { InternalReactlitState } from './internal-state';
+import { useCallback } from "react";
+import { deepEqual } from "../utils/deep-equal";
+import { InternalReactlitState } from "./internal-state";
+import { ReactlitContext, StateBase } from "./types";
export function useReactlitSet({
- state,
- setState,
- setPreviousState,
+ state,
+ setState,
+ setPreviousState,
}: InternalReactlitState) {
- return useCallback['set']>(
- (key, value) => {
- setState(key, (prev) => {
- if (deepEqual(prev, value)) return prev;
- return value;
- });
- setPreviousState(state);
- return value;
- },
- [setState, state, setPreviousState]
- );
+ return useCallback["set"]>(
+ (key, value) => {
+ setState(key, (prev) => {
+ if (deepEqual(prev, value)) return prev;
+ return value;
+ });
+ setPreviousState(state);
+ return value;
+ },
+ [setState, state, setPreviousState],
+ );
}
diff --git a/libs/core/src/builtins/types.ts b/libs/core/src/builtins/types.ts
index e7dca15..3fd9bbc 100644
--- a/libs/core/src/builtins/types.ts
+++ b/libs/core/src/builtins/types.ts
@@ -1,65 +1,67 @@
-import { Dispatch, ReactNode, SetStateAction } from 'react';
-import { DisplayArgs } from './display';
-import { ViewArgs } from './view';
+import { Dispatch, ReactNode, SetStateAction } from "react";
+import { DisplayArgs } from "./display";
+import { ViewArgs } from "./view";
export type StateBase = Record;
export interface ViewComponentProps {
- stateKey: string;
- value: T;
- setValue: Dispatch;
- display: (...args: DisplayArgs) => void;
- view: (
- ...args: ViewArgs
- ) => R;
+ stateKey: string;
+ value: T;
+ setValue: Dispatch;
+ display: (...args: DisplayArgs) => void;
+ view: (
+ ...args: ViewArgs
+ ) => R;
}
export type ViewComponent = React.FC>;
export interface ViewDefinition {
- component: ViewComponent;
- getReturnValue?: (props: ViewComponentProps) => ReturnType;
+ component: ViewComponent;
+ getReturnValue?: (props: ViewComponentProps) => ReturnType;
}
+// biome-ignore lint/suspicious/noExplicitAny:
export interface ReactlitContext {
- view: (...args: ViewArgs) => R;
- set: (key: K, value: T[K]) => T[K];
- display: (...args: DisplayArgs) => void;
- changed: (...keys: (keyof T)[]) => boolean;
- trigger: () => void;
- state: T;
+ view: (...args: ViewArgs) => R;
+ set: (key: K, value: T[K]) => T[K];
+ display: (...args: DisplayArgs) => void;
+ changed: (...keys: (keyof T)[]) => boolean;
+ trigger: () => void;
+ state: T;
}
export type ReactlitStateSetter = (
- key: K,
- value: SetStateAction
+ key: K,
+ value: SetStateAction,
) => void;
export type ReactlitFunction<
- T extends StateBase = any,
- C extends ReactlitContext = ReactlitContext
+ // biome-ignore lint/suspicious/noExplicitAny:
+ T extends StateBase = any,
+ C extends ReactlitContext = ReactlitContext,
> = (ctx: C) => Promise;
export type ReactlitProps = {
- state?: T;
- setState?: ReactlitStateSetter;
- /**
- * Render function to display a loading message
- */
- renderLoading?: (rendering: boolean) => ReactNode;
- /**
- * Render function to display an error message
- */
- renderError?: (props: {
- error: any;
- resetErrorBoundary: (...args: any[]) => void;
- }) => ReactNode;
- /**
- * Whether to log debug messages to the console
- */
- debug?: boolean;
- /**
- * Function for the Reactlit rendering logic
- */
- children: ReactlitFunction;
+ state?: T;
+ setState?: ReactlitStateSetter;
+ /**
+ * Render function to display a loading message
+ */
+ renderLoading?: (rendering: boolean) => ReactNode;
+ /**
+ * Render function to display an error message
+ */
+ renderError?: (props: {
+ error: unknown;
+ resetErrorBoundary: (...args: unknown[]) => void;
+ }) => ReactNode;
+ /**
+ * Whether to log debug messages to the console
+ */
+ debug?: boolean;
+ /**
+ * Function for the Reactlit rendering logic
+ */
+ children: ReactlitFunction;
};
diff --git a/libs/core/src/builtins/view.ts b/libs/core/src/builtins/view.ts
index 7c0259e..3e5f9d3 100644
--- a/libs/core/src/builtins/view.ts
+++ b/libs/core/src/builtins/view.ts
@@ -1,74 +1,75 @@
-import { useCallback } from 'react';
+import { useCallback } from "react";
+import { tail } from "../utils/tail";
+import { Wrapper } from "../wrappers";
import {
- ReactlitContext,
- StateBase,
- ViewComponent,
- ViewComponentProps,
- ViewDefinition,
-} from './types';
-import { Wrapper } from '../wrappers';
-import { tail } from '../utils/tail';
+ ReactlitContext,
+ StateBase,
+ ViewComponent,
+ ViewComponentProps,
+ ViewDefinition,
+} from "./types";
export function defineView(
- component: ViewComponent
+ component: ViewComponent,
): ViewDefinition {
- return { component };
+ return { component };
}
export function defineTransformView(
- component: ViewComponent,
- getReturnValue: (props: ViewComponentProps) => ReturnType
+ component: ViewComponent,
+ getReturnValue: (props: ViewComponentProps) => ReturnType,
): ViewDefinition {
- return { component, getReturnValue };
+ return { component, getReturnValue };
}
export type ViewArgs = [
- key: K,
- ...wrappers: Wrapper[],
- def: ViewDefinition
+ key: K,
+ ...wrappers: Wrapper[],
+ def: ViewDefinition,
];
export function normalizeViewArgs<
- T extends StateBase,
- K extends keyof T & string,
- V,
- R
+ T extends StateBase,
+ K extends keyof T & string,
+ V,
+ R,
>(args: ViewArgs) {
- const [key, ...restArgs] = args;
- const [wrappers, def] = tail(restArgs);
- return { key, wrappers, def };
+ const [key, ...restArgs] = args;
+ const [wrappers, def] = tail(restArgs);
+ return { key, wrappers, def };
}
function makeViewFunction({
- set,
- display,
- state,
-}: Pick, 'set' | 'display' | 'state'>) {
- return function view(
- ...args: ViewArgs
- ) {
- const { key, wrappers, def } = normalizeViewArgs(args);
- const { component, getReturnValue } = def;
- const props: ViewComponentProps = {
- stateKey: key,
- value: state[key] as V,
- setValue: (value: any) => set(key, value),
- display,
- view,
- };
- display(key, ...wrappers, component(props));
- return getReturnValue ? getReturnValue(props) : (state[key] as R);
- };
+ set,
+ display,
+ state,
+}: Pick, "set" | "display" | "state">) {
+ return function view(
+ ...args: ViewArgs
+ ) {
+ const { key, wrappers, def } = normalizeViewArgs(args);
+ const { component, getReturnValue } = def;
+ const props: ViewComponentProps = {
+ stateKey: key,
+ value: state[key] as V,
+ // biome-ignore lint/suspicious/noExplicitAny:
+ setValue: (value: any) => set(key, value),
+ display,
+ view,
+ };
+ display(key, ...wrappers, component(props));
+ return getReturnValue ? getReturnValue(props) : (state[key] as R);
+ };
}
export function useReactlitView({
- set,
- display,
- state,
-}: Pick, 'set' | 'display' | 'state'>) {
- return useCallback['view']>(
- (...args: ViewArgs) =>
- makeViewFunction({ set, display, state })(...args),
- [state, set, display]
- );
+ set,
+ display,
+ state,
+}: Pick, "set" | "display" | "state">) {
+ return useCallback["view"]>(
+ (...args: ViewArgs) =>
+ makeViewFunction({ set, display, state })(...args),
+ [state, set, display],
+ );
}
diff --git a/libs/core/src/hooks/use-deep-memo.ts b/libs/core/src/hooks/use-deep-memo.ts
index facefa3..5cb750d 100644
--- a/libs/core/src/hooks/use-deep-memo.ts
+++ b/libs/core/src/hooks/use-deep-memo.ts
@@ -1,5 +1,5 @@
-import { useRef } from 'react';
-import { deepEqual } from '../utils/deep-equal';
+import { useRef } from "react";
+import { deepEqual } from "../utils/deep-equal";
/**
* Memoize a result using deep equality. This hook has two advantages over
@@ -9,14 +9,14 @@ import { deepEqual } from '../utils/deep-equal';
* optimization (see https://reactjs.org/docs/hooks-reference.html#usememo).
*/
export function useDeepMemo(
- memoFn: () => TValue,
- key: TKey
+ memoFn: () => TValue,
+ key: TKey,
): TValue {
- const ref = useRef<{ key: TKey; value: TValue }>(null);
+ const ref = useRef<{ key: TKey; value: TValue }>(null);
- if (!ref.current || !deepEqual(key, ref.current.key)) {
- ref.current = { key, value: memoFn() };
- }
+ if (!ref.current || !deepEqual(key, ref.current.key)) {
+ ref.current = { key, value: memoFn() };
+ }
- return ref.current.value;
+ return ref.current.value;
}
diff --git a/libs/core/src/hooks/use-reactlit-state.ts b/libs/core/src/hooks/use-reactlit-state.ts
index 8c31439..3bcc433 100644
--- a/libs/core/src/hooks/use-reactlit-state.ts
+++ b/libs/core/src/hooks/use-reactlit-state.ts
@@ -1,67 +1,70 @@
-import { SetStateAction, useCallback, useMemo, useState } from 'react';
+import { SetStateAction, useCallback, useMemo, useState } from "react";
export type StateAndSetter = [T, React.Dispatch>];
export function isSetStateFunction(
- value: T | ((prev: T) => void)
+ value: T | ((prev: T) => void),
): value is (prev: T) => void {
- return typeof value === 'function';
+ return typeof value === "function";
}
export type ComboState> = {
- [K in keyof T]: StateAndSetter;
+ [K in keyof T]: StateAndSetter;
};
export type ComboStateResult> = [
- T,
- ComboStateSetter
+ T,
+ ComboStateSetter,
];
export type ComboStateSetter> = <
- K extends keyof T
+ K extends keyof T,
>(
- key: K,
- value: SetStateAction
+ key: K,
+ value: SetStateAction,
) => void;
export function useCompoundReaclitState<
- T extends Record,
- U extends Record
+ T extends Record,
+ U extends Record,
>(
- states: ComboState,
- defaultStateSetter: StateAndSetter
+ states: ComboState,
+ defaultStateSetter: StateAndSetter,
): ComboStateResult {
- const [defaultState, defaultSetter] = defaultStateSetter;
- const state = useMemo(() => {
- const newState: any = {};
- for (const key in states) {
- newState[key] = states[key][0];
- }
- return { ...newState, ...defaultState };
- }, [states, defaultState]);
+ const [defaultState, defaultSetter] = defaultStateSetter;
+ const state = useMemo(() => {
+ // biome-ignore lint/suspicious/noExplicitAny:
+ const newState: any = {};
+ for (const key in states) {
+ newState[key] = states[key][0];
+ }
+ return { ...newState, ...defaultState };
+ }, [states, defaultState]);
- const setState = useCallback>(
- (key: K, value: SetStateAction<(T & U)[K]>) => {
- if (key in states) {
- states[key as keyof T][1](value as SetStateAction);
- } else {
- defaultSetter((p) => ({
- ...p,
- [key]: isSetStateFunction(value)
- ? value(p[key as keyof U] as any)
- : value,
- }));
- }
- },
- [states, defaultSetter]
- );
+ const setState = useCallback>(
+ (key: K, value: SetStateAction<(T & U)[K]>) => {
+ if (key in states) {
+ states[key as keyof T][1](value as SetStateAction);
+ } else {
+ defaultSetter((p) => ({
+ ...p,
+ [key]: isSetStateFunction(value)
+ ? // biome-ignore lint/suspicious/noExplicitAny:
+ value(p[key as keyof U] as any)
+ : value,
+ }));
+ }
+ },
+ [states, defaultSetter],
+ );
- return [state, setState];
+ return [state, setState];
}
export function useReactlitState>(
- initialState: T
+ initialState: T,
): ComboStateResult {
- const empty = useMemo>(() => ({}), []);
- return useCompoundReaclitState(empty, useState(initialState));
+ // biome-ignore lint/complexity/noBannedTypes:
+ const empty = useMemo>(() => ({}), []);
+ return useCompoundReaclitState(empty, useState(initialState));
}
diff --git a/libs/core/src/hooks/use-reactlit.spec.tsx b/libs/core/src/hooks/use-reactlit.spec.tsx
index c86bdb3..e4117d7 100644
--- a/libs/core/src/hooks/use-reactlit.spec.tsx
+++ b/libs/core/src/hooks/use-reactlit.spec.tsx
@@ -1,32 +1,32 @@
-import '@testing-library/jest-dom';
-import { render, screen } from '@testing-library/react';
-import { definePlugin, useReactlit } from './use-reactlit';
-import { useReactlitState } from './use-reactlit-state';
+import "@testing-library/jest-dom";
+import { render, screen } from "@testing-library/react";
+import { definePlugin, useReactlit } from "./use-reactlit";
+import { useReactlitState } from "./use-reactlit-state";
-test('reactlit plugin', async () => {
- let pluginRan = false;
- const TestPlugin = definePlugin(({ display }) => ({
- runPlugin: () => {
- pluginRan = true;
- return display('Hello from plugin');
- },
- }));
+test("reactlit plugin", async () => {
+ let pluginRan = false;
+ const TestPlugin = definePlugin(({ display }) => ({
+ runPlugin: () => {
+ pluginRan = true;
+ return display("Hello from plugin");
+ },
+ }));
- function PluginTest() {
- const [state, setState] = useReactlitState({
- firstName: 'John',
- lastName: 'Doe',
- });
- const Reactlit = useReactlit(TestPlugin);
- return (
-
- {async (ctx) => {
- ctx.runPlugin();
- }}
-
- );
- }
- render();
- expect(pluginRan).toBe(true);
- expect(await screen.findByText('Hello from plugin')).toBeVisible();
+ function PluginTest() {
+ const [state, setState] = useReactlitState({
+ firstName: "John",
+ lastName: "Doe",
+ });
+ const Reactlit = useReactlit(TestPlugin);
+ return (
+
+ {async (ctx) => {
+ ctx.runPlugin();
+ }}
+
+ );
+ }
+ render();
+ expect(pluginRan).toBe(true);
+ expect(await screen.findByText("Hello from plugin")).toBeVisible();
});
diff --git a/libs/core/src/hooks/use-reactlit.tsx b/libs/core/src/hooks/use-reactlit.tsx
index 06bae6a..709d8f8 100644
--- a/libs/core/src/hooks/use-reactlit.tsx
+++ b/libs/core/src/hooks/use-reactlit.tsx
@@ -1,59 +1,63 @@
-import { useCallback, useMemo } from 'react';
-import { Reactlit } from '../reactlit';
+import { useCallback, useMemo } from "react";
import {
- ReactlitContext,
- ReactlitFunction,
- ReactlitProps,
- StateBase,
-} from '../builtins/types';
+ ReactlitContext,
+ ReactlitFunction,
+ ReactlitProps,
+ StateBase,
+} from "../builtins/types";
+import { Reactlit } from "../reactlit";
-export type ReactlitPlugin = (ctx: ReactlitContext) => C;
+// biome-ignore lint/suspicious/noExplicitAny:
+export type ReactlitPlugin = (ctx: ReactlitContext) => C;
export function definePlugin(plugin: ReactlitPlugin) {
- return plugin;
+ return plugin;
}
type GenericPluginResult = Plugin extends ReactlitPlugin
- ? C
- : never;
+ ? C
+ : never;
type ApplyPlugins = Plugins extends readonly [
- infer Plugin,
- ...infer Rest
+ infer Plugin,
+ ...infer Rest,
]
- ? GenericPluginResult & ApplyPlugins
- : Record;
+ ? GenericPluginResult & ApplyPlugins
+ : Record;
export type ReactlitFunctionWithPlugins<
- T extends StateBase,
- P extends readonly ReactlitPlugin[]
+ T extends StateBase,
+ P extends readonly ReactlitPlugin[],
> = ReactlitFunction & ReactlitContext>;
-export function useReactlit[]>(
- ...plugins: P
+export function useReactlit
(
+ ...plugins: P
) {
- return useMemo(() => {
- function CustomReactlit({
- children,
- ...props
- }: Omit, 'children'> & {
- children: ReactlitFunction & ReactlitContext>;
- }) {
- const func = useCallback(
- async (ctx: ReactlitContext) => {
- return children(
- (plugins ?? []).reduce(
- (acc, plugin) => ({ ...acc, ...plugin(ctx) }),
- ctx as any
- )
- );
- },
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [children, ...(plugins ?? [])]
- );
- return {func};
- }
- return CustomReactlit;
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [...plugins]);
+ // biome-ignore lint/correctness/useExhaustiveDependencies:
+ return useMemo(() => {
+ function CustomReactlit({
+ children,
+ ...props
+ }: Omit, "children"> & {
+ children: ReactlitFunction & ReactlitContext>;
+ }) {
+ // biome-ignore lint/correctness/useExhaustiveDependencies:
+ const func = useCallback(
+ async (ctx: ReactlitContext) => {
+ return children(
+ (plugins ?? []).reduce(
+ (acc, plugin) => Object.assign({}, acc, plugin(ctx)),
+ // biome-ignore lint/suspicious/noExplicitAny:
+ ctx as any,
+ ),
+ );
+ },
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [children, ...(plugins ?? [])],
+ );
+ return {func};
+ }
+ return CustomReactlit;
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [...plugins]);
}
diff --git a/libs/core/src/index.ts b/libs/core/src/index.ts
index 76fd3bb..5004dd9 100644
--- a/libs/core/src/index.ts
+++ b/libs/core/src/index.ts
@@ -1,19 +1,19 @@
-export * from './reactlit';
-export * from './builtins/types';
-export { defineView, defineTransformView } from './builtins/view';
-export * from './hooks/use-reactlit';
-export * from './hooks/use-reactlit-state';
-export * from './wrappers';
-export * from './inputs/layout.view';
-export * from './plugins/data-fetching';
+export * from "./reactlit";
+export * from "./builtins/types";
+export { defineView, defineTransformView } from "./builtins/view";
+export * from "./hooks/use-reactlit";
+export * from "./hooks/use-reactlit-state";
+export * from "./wrappers";
+export * from "./inputs/layout.view";
+export * from "./plugins/data-fetching";
// see https://github.com/mdx-js/mdx/issues/2487
-import type { JSX as Jsx } from 'react/jsx-runtime';
+import type { JSX as Jsx } from "react/jsx-runtime";
declare global {
- namespace JSX {
- type ElementClass = Jsx.ElementClass;
- type Element = Jsx.Element;
- type IntrinsicElements = Jsx.IntrinsicElements;
- }
+ namespace JSX {
+ type ElementClass = Jsx.ElementClass;
+ type Element = Jsx.Element;
+ type IntrinsicElements = Jsx.IntrinsicElements;
+ }
}
diff --git a/libs/core/src/inputs/layout.view.tsx b/libs/core/src/inputs/layout.view.tsx
index 6584844..87c56c9 100644
--- a/libs/core/src/inputs/layout.view.tsx
+++ b/libs/core/src/inputs/layout.view.tsx
@@ -1,139 +1,141 @@
-import { Fragment, useEffect, useRef } from 'react';
-import { normalizeDisplayArgs } from '../builtins/display';
+import { Fragment, useEffect, useRef } from "react";
+import { normalizeDisplayArgs } from "../builtins/display";
import {
- ReactlitContext,
- StateBase,
- ViewComponentProps,
-} from '../builtins/types';
+ ReactlitContext,
+ StateBase,
+ ViewComponentProps,
+} from "../builtins/types";
import {
- defineTransformView,
- normalizeViewArgs,
- ViewArgs,
-} from '../builtins/view';
-import tunnel from '../utils/tunnel';
-import { applySimpleWrapper, SimpleWrapper, Wrapper } from '../wrappers';
+ ViewArgs,
+ defineTransformView,
+ normalizeViewArgs,
+} from "../builtins/view";
+import tunnel from "../utils/tunnel";
+import { SimpleWrapper, Wrapper, applySimpleWrapper } from "../wrappers";
export type Tunnel = ReturnType;
export type Repeat<
- T,
- C extends number,
- Result extends T[] = [],
- Counter extends any[] = []
-> = Counter['length'] extends C
- ? Result
- : Repeat;
+ T,
+ C extends number,
+ Result extends T[] = [],
+ // biome-ignore lint/suspicious/noExplicitAny:
+ Counter extends any[] = [],
+> = Counter["length"] extends C
+ ? Result
+ : Repeat;
export type LayoutSlot = Pick<
- ReactlitContext,
- 'display' | 'view'
+ ReactlitContext,
+ "display" | "view"
>;
// during initialization we create empty layout slots, these are only temporary
// until the state gets set up
export function createEmptyLayoutSlot<
- T extends StateBase = StateBase
+ T extends StateBase = StateBase,
>(): LayoutSlot {
- return {
- display: () => <>>,
- view(...args: ViewArgs) {
- return undefined as R;
- },
- };
+ return {
+ display: () => <>>,
+ view(...args: ViewArgs) {
+ return undefined as R;
+ },
+ };
}
export function createLayoutSlot(
- ctx: Pick, 'display' | 'view'>,
- t: ReturnType
+ ctx: Pick, "display" | "view">,
+ t: ReturnType,
): LayoutSlot {
- const TunnelWrapper: Wrapper = ({ stateKey, position, children }) => {
- return (
-
- {children}
-
- );
- };
- return {
- display(...args) {
- const { manualKey, wrappers, node } = normalizeDisplayArgs(args);
- if (manualKey) {
- ctx.display(manualKey, TunnelWrapper, ...wrappers, node);
- } else {
- ctx.display(TunnelWrapper, ...wrappers, node);
- }
- },
- view(...args: ViewArgs) {
- const { key, wrappers, def } = normalizeViewArgs(args);
- return ctx.view(key, TunnelWrapper, ...wrappers, def);
- },
- };
+ const TunnelWrapper: Wrapper = ({ stateKey, position, children }) => {
+ return (
+
+ {children}
+
+ );
+ };
+ return {
+ display(...args) {
+ const { manualKey, wrappers, node } = normalizeDisplayArgs(args);
+ if (manualKey) {
+ ctx.display(manualKey, TunnelWrapper, ...wrappers, node);
+ } else {
+ ctx.display(TunnelWrapper, ...wrappers, node);
+ }
+ },
+ view(...args: ViewArgs) {
+ const { key, wrappers, def } = normalizeViewArgs(args);
+ return ctx.view(key, TunnelWrapper, ...wrappers, def);
+ },
+ };
}
export function LayoutViewComponent({
- slots,
- value,
- setValue,
- slotWrapper,
+ slots,
+ value,
+ setValue,
+ slotWrapper,
}: {
- slots: N;
- slotWrapper?: SimpleWrapper;
+ slots: N;
+ slotWrapper?: SimpleWrapper;
} & ViewComponentProps>) {
- const tunnels = useRef([]);
- useEffect(() => {
- if (tunnels.current.length !== slots) {
- tunnels.current = Array.from({ length: slots }, () => tunnel());
- setValue(tunnels.current as Repeat);
- }
- }, [slots, setValue]);
- if (!value) return null;
- return (
- <>
- {(value as Tunnel[])
- .map((t) => t.Out)
- .map((Slot, index) => (
-
- {applySimpleWrapper(, slotWrapper)}
-
- ))}
- >
- );
+ const tunnels = useRef([]);
+ useEffect(() => {
+ if (tunnels.current.length !== slots) {
+ tunnels.current = Array.from({ length: slots }, () => tunnel());
+ setValue(tunnels.current as Repeat);
+ }
+ }, [slots, setValue]);
+ if (!value) return null;
+ return (
+ <>
+ {(value as Tunnel[])
+ .map((t) => t.Out)
+ .map((Slot, index) => (
+ // biome-ignore lint/suspicious/noArrayIndexKey:
+
+ {applySimpleWrapper(, slotWrapper)}
+
+ ))}
+ >
+ );
}
export type LayoutViewType = Repeat | undefined;
export function defaultLayoutState(
- slots: N
+ slots: N,
): LayoutViewType {
- return undefined;
+ return undefined;
}
export function LayoutView(
- slots: N,
- slotWrapper?: SimpleWrapper
+ slots: N,
+ slotWrapper?: SimpleWrapper,
) {
- return defineTransformView<
- Repeat | undefined,
- Repeat, N>
- >(
- (viewProps) => (
-
- ),
- ({ value, display, view }) => {
- const tunnels = (value ?? []) as Tunnel[];
- if (tunnels.length !== slots) {
- return Array.from({ length: slots }, createEmptyLayoutSlot) as Repeat<
- LayoutSlot,
- N
- >;
- }
- const subContext = tunnels.map((t) =>
- createLayoutSlot({ display, view }, t)
- );
- return subContext as Repeat, N>;
- }
- );
+ return defineTransformView<
+ Repeat | undefined,
+ Repeat, N>
+ >(
+ (viewProps) => (
+
+ ),
+ ({ value, display, view }) => {
+ const tunnels = (value ?? []) as Tunnel[];
+ if (tunnels.length !== slots) {
+ return Array.from({ length: slots }, createEmptyLayoutSlot) as Repeat<
+ LayoutSlot,
+ N
+ >;
+ }
+ const subContext = tunnels.map((t) =>
+ createLayoutSlot({ display, view }, t),
+ );
+ return subContext as Repeat, N>;
+ },
+ );
}
diff --git a/libs/core/src/plugins/data-fetching.ts b/libs/core/src/plugins/data-fetching.ts
index 2c75b0b..87b92ad 100644
--- a/libs/core/src/plugins/data-fetching.ts
+++ b/libs/core/src/plugins/data-fetching.ts
@@ -1,102 +1,102 @@
import {
- EnsureQueryDataOptions,
- QueryClient,
- QueryClientConfig,
- QueryKey,
- QueryObserver,
- Updater,
-} from '@tanstack/react-query';
-import { definePlugin, ReactlitPlugin } from '../hooks/use-reactlit';
+ EnsureQueryDataOptions,
+ QueryClient,
+ QueryClientConfig,
+ QueryKey,
+ QueryObserver,
+ Updater,
+} from "@tanstack/react-query";
+import { ReactlitPlugin, definePlugin } from "../hooks/use-reactlit";
export class DataFetcher {
- constructor(
- public client: QueryClient,
- private trigger: () => void,
- private key: QueryKey,
- private fn: () => Promise,
- private options?: Partial<
- EnsureQueryDataOptions
- >
- ) {}
+ constructor(
+ public client: QueryClient,
+ private trigger: () => void,
+ private key: QueryKey,
+ private fn: () => Promise,
+ private options?: Partial<
+ EnsureQueryDataOptions
+ >,
+ ) {}
- get() {
- if (this.key.some((k) => k === undefined)) {
- // eslint-disable-next-line no-console
- console.warn(
- 'One or more of the data fetching keys are undefined, this can result in unexpected behavior'
- );
- }
- const state = this.client.getQueryState(this.key);
- if (state?.status === 'error') {
- throw state.error;
- }
- this.client.ensureQueryData({
- queryKey: this.key,
- queryFn: async () => {
- // trigger before so we can see the loading state
- this.trigger();
- return this.fn();
- },
- ...this.options,
- });
- return this.client.getQueryData(this.key) as T;
- }
+ get() {
+ if (this.key.some((k) => k === undefined)) {
+ // eslint-disable-next-line no-console
+ console.warn(
+ "One or more of the data fetching keys are undefined, this can result in unexpected behavior",
+ );
+ }
+ const state = this.client.getQueryState(this.key);
+ if (state?.status === "error") {
+ throw state.error;
+ }
+ this.client.ensureQueryData({
+ queryKey: this.key,
+ queryFn: async () => {
+ // trigger before so we can see the loading state
+ this.trigger();
+ return this.fn();
+ },
+ ...this.options,
+ });
+ return this.client.getQueryData(this.key) as T;
+ }
- update(updater: Updater) {
- this.client.setQueryData(this.key, updater);
- this.trigger();
- }
+ update(updater: Updater) {
+ this.client.setQueryData(this.key, updater);
+ this.trigger();
+ }
- async refetch() {
- await this.client.invalidateQueries({
- queryKey: this.key,
- refetchType: 'all',
- });
- this.trigger();
- }
+ async refetch() {
+ await this.client.invalidateQueries({
+ queryKey: this.key,
+ refetchType: "all",
+ });
+ this.trigger();
+ }
- isFetching() {
- const state = this.client.getQueryState(this.key);
- return (
- !state || state?.status === 'pending' || state?.fetchStatus === 'fetching'
- );
- }
+ isFetching() {
+ const state = this.client.getQueryState(this.key);
+ return (
+ !state || state?.status === "pending" || state?.fetchStatus === "fetching"
+ );
+ }
}
export type DataFetchingContext = {
- fetcher(
- key: QueryKey,
- fn: () => Promise,
- options?: Partial>
- ): DataFetcher;
+ fetcher(
+ key: QueryKey,
+ fn: () => Promise,
+ options?: Partial>,
+ ): DataFetcher;
};
export function makeDataFetchingPlugin(
- config?: QueryClientConfig
+ config?: QueryClientConfig,
): ReactlitPlugin {
- const client = new QueryClient(config);
- let isMounted = false;
- return definePlugin((ctx) => {
- if (!isMounted) {
- client.mount();
- const observer = new QueryObserver(client, {
- queryKey: [],
- });
- observer.subscribe(() => {
- ctx.trigger();
- });
- isMounted = true;
- }
- return {
- fetcher(
- key: QueryKey,
- fn: () => Promise,
- options?: Partial>
- ) {
- return new DataFetcher(client, ctx.trigger, key, fn, options);
- },
- };
- });
+ const client = new QueryClient(config);
+ let isMounted = false;
+ return definePlugin((ctx) => {
+ if (!isMounted) {
+ client.mount();
+ const observer = new QueryObserver(client, {
+ queryKey: [],
+ });
+ observer.subscribe(() => {
+ ctx.trigger();
+ });
+ isMounted = true;
+ }
+ return {
+ fetcher(
+ key: QueryKey,
+ fn: () => Promise,
+ options?: Partial>,
+ ) {
+ return new DataFetcher(client, ctx.trigger, key, fn, options);
+ },
+ };
+ });
}
export const DataFetchingPlugin = makeDataFetchingPlugin();
diff --git a/libs/core/src/reactlit.spec.tsx b/libs/core/src/reactlit.spec.tsx
index 3611a8e..b299579 100644
--- a/libs/core/src/reactlit.spec.tsx
+++ b/libs/core/src/reactlit.spec.tsx
@@ -1,32 +1,31 @@
-import { render, screen } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import '@testing-library/jest-dom';
-import { defineView, Reactlit } from '.';
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { Reactlit, defineView } from ".";
-export const TextInput = defineView