Merge pull request #1 from samuelcolvin/refactor-package

Refactor
This commit is contained in:
Samuel Colvin
2023-11-12 21:18:56 +00:00
committed by GitHub
47 changed files with 424 additions and 69 deletions

View File

@@ -6,8 +6,9 @@ module.exports = {
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
'prettier',
],
ignorePatterns: ['dist', '.eslintrc.cjs', 'vite.config.ts'],
ignorePatterns: ['dist', '.eslintrc.cjs', 'demo/vite.config.ts'],
parser: '@typescript-eslint/parser',
plugins: ['react', '@typescript-eslint', 'react-refresh', 'simple-import-sort'],
rules: {

79
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,79 @@
name: CI
on:
push:
branches:
- main
tags:
- '**'
pull_request:
types: [opened, synchronize]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- uses: actions/setup-node@v3
with:
node-version: 18
- run: pip install -r python/requirements/all.txt
- run: npm install
- uses: pre-commit/action@v3.0.0
with:
extra_args: --all-files
env:
SKIP: no-commit-to-branch
check: # This job does nothing and is only used for the branch protection
if: always()
needs: [lint]
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@release/v1
id: all-green
with:
jobs: ${{ toJSON(needs) }}
release:
needs: [check]
if: "success() && startsWith(github.ref, 'refs/tags/')"
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write
steps:
- uses: actions/checkout@v3
- name: set up python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: install
run: pip install -U build
- name: check version
id: check-version
uses: samuelcolvin/check-python-version@v4.1
with:
version_file_path: 'python/fastui/__init__.py'
- name: build
run: python -m build
- name: Upload package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

2
.gitignore vendored
View File

@@ -28,3 +28,5 @@ dist-ssr
__pycache__/
/.logfire/
/frontend-dist/
/scratch/

41
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,41 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
- id: no-commit-to-branch
- id: check-yaml
- id: check-toml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: local
hooks:
- id: python-format
name: python-format
types_or: [python]
entry: make format
language: system
pass_filenames: false
- id: python-typecheck
name: python-typecheck
types_or: [python]
entry: make typecheck
language: system
pass_filenames: false
- id: react-prettier
name: react-prettier
types_or: [javascript, jsx, ts, tsx, css, json, markdown]
entry: npm run prettier
language: system
- id: react-lint
name: react-lint
types_or: [ts, tsx]
entry: npm run lint-fix
language: system
pass_filenames: false
- id: react-typecheck
name: react-typecheck
types_or: [ts, tsx]
entry: npm run typecheck
language: system
pass_filenames: false

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2023 to present Samuel Colvin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

44
Makefile Normal file
View File

@@ -0,0 +1,44 @@
.DEFAULT_GOAL:=all
paths = python
.PHONY: install
install:
pip install -U pip pre-commit pip-tools
pip install -r python/requirements/all.txt
pre-commit install
.PHONY: update-lockfiles
update-lockfiles:
@echo "Updating requirements files using pip-compile"
pip-compile -q --strip-extras -o python/requirements/lint.txt python/requirements/lint.in
pip-compile -q --strip-extras -o python/requirements/pyproject.txt pyproject.toml
pip install --dry-run -r python/requirements/all.txt
.PHONY: format
format:
ruff check --fix-only $(paths)
ruff format $(paths)
.PHONY: lint
lint:
ruff check $(paths)
ruff format --check $(paths)
.PHONY: typecheck
typecheck:
pyright python/fastui
.PHONY: test
test:
coverage run -m pytest tests
.PHONY: testcov
testcov: test
coverage html
.PHONY: dev
dev:
uvicorn demo.server:app --reload
.PHONY: all
all: testcov lint

8
README.md Normal file
View File

@@ -0,0 +1,8 @@
# FastUI
[![CI](https://github.com/samuelcolvin/FastUI/workflows/CI/badge.svg?event=push)](https://github.com/samuelcolvin/FastUI/actions?query=event%3Apush+branch%3Amain+workflow%3ACI)
[![pypi](https://img.shields.io/pypi/v/fastui.svg)](https://pypi.python.org/pypi/fastui)
[![versions](https://img.shields.io/pypi/pyversions/fastui.svg)](https://github.com/samuelcolvin/FastUI)
[![license](https://img.shields.io/github/license/samuelcolvin/FastUI.svg)](https://github.com/samuelcolvin/FastUI/blob/main/LICENSE)
WIP

View File

@@ -3,20 +3,15 @@ from __future__ import annotations as _annotations
from datetime import date
from fastapi import FastAPI
from pydantic import RootModel, BaseModel, Field
from pydantic import BaseModel, Field
import components as c
from components import AnyComponent
from components.events import PageEvent, GoToEvent
from fastui import components as c
from fastui import FastUI, PageEvent, GoToEvent, Display, AnyComponent
app = FastAPI()
class FastUi(RootModel):
root: AnyComponent
@app.get('/api/', response_model=FastUi, response_model_exclude_none=True)
@app.get('/api/', response_model=FastUI, response_model_exclude_none=True)
def read_root() -> AnyComponent:
return c.Page(
children=[
@@ -44,7 +39,7 @@ class MyTableRow(BaseModel):
enabled: bool | None = None
@app.get('/api/table', response_model=FastUi, response_model_exclude_none=True)
@app.get('/api/table', response_model=FastUI, response_model_exclude_none=True)
def read_foo() -> AnyComponent:
return c.Page(
children=[
@@ -57,7 +52,7 @@ def read_foo() -> AnyComponent:
],
columns=[
c.Column(field='name', on_click=GoToEvent(url='/api/more/{id}/')),
c.Column(field='dob', display=c.Display.date),
c.Column(field='dob', display=Display.date),
c.Column(field='enabled'),
]
)

View File

@@ -1,6 +1,6 @@
import { FastUI, ClassNameGenerator, CustomRender } from './FastUI'
import { FastUI, ClassNameGenerator, CustomRender } from 'fastui'
export default function App () {
export default function App() {
return (
<div className="app">
<FastUI rootUrl="/api" classNameGenerator={bootstrapClassName} customRender={customRender} />

View File

@@ -7,5 +7,5 @@ import './main.scss'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
</React.StrictMode>,
)

View File

@@ -18,7 +18,10 @@
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
"noFallthroughCasesInSwitch": true,
"paths": {
"fastui": ["../react/fastui"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]

View File

@@ -17,6 +17,7 @@ export default () => {
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
fastui: path.resolve(__dirname, '../react/fastui'),
},
},
server: serverConfig,

View File

@@ -1,11 +1,11 @@
{
"name": "fastui-demo",
"name": "fastui",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "fastui-demo",
"name": "fastui",
"version": "0.0.0",
"dependencies": {
"react": "^18.2.0",
@@ -26,7 +26,6 @@
"eslint-plugin-react-refresh": "^0.4.4",
"eslint-plugin-simple-import-sort": "^10.0.0",
"prettier": "^3.0.3",
"sass": "^1.67.0",
"typescript": "^5.0.2",
"vite": "^4.4.5"
}
@@ -1054,6 +1053,8 @@
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
@@ -1231,6 +1232,8 @@
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true,
"optional": true,
"peer": true,
"engines": {
"node": ">=8"
}
@@ -1349,6 +1352,8 @@
"url": "https://paulmillr.com/funding/"
}
],
"optional": true,
"peer": true,
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
@@ -1370,6 +1375,8 @@
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"is-glob": "^4.0.1"
},
@@ -2538,7 +2545,9 @@
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz",
"integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==",
"dev": true
"dev": true,
"optional": true,
"peer": true
},
"node_modules/import-fresh": {
"version": "3.3.0",
@@ -2641,6 +2650,8 @@
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"binary-extensions": "^2.0.0"
},
@@ -3170,6 +3181,8 @@
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"optional": true,
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -3547,6 +3560,8 @@
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"picomatch": "^2.2.1"
},
@@ -3729,6 +3744,8 @@
"resolved": "https://registry.npmjs.org/sass/-/sass-1.67.0.tgz",
"integrity": "sha512-SVrO9ZeX/QQyEGtuZYCVxoeAL5vGlYjJ9p4i4HFuekWl8y/LtJ7tJc10Z+ck1c8xOuoBm2MYzcLfTAffD0pl/A==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",

View File

@@ -1,16 +1,18 @@
{
"name": "fastui-demo",
"name": "fastui",
"private": true,
"version": "0.0.0",
"type": "module",
"entry": "src/index.tsx",
"scripts": {
"dev": "vite",
"dev": "vite demo",
"typecheck": "tsc --noEmit",
"build": "tsc",
"typewatch": "tsc --noEmit --watch",
"build": "tsc && vite build",
"lint": "eslint . --ext .ts,.tsx --report-unused-disable-directives --max-warnings 0",
"format": "prettier . --write -- '**/*.{ts,tsx,js,css,json,md}' && npm run lint -- --fix",
"preview": "vite preview"
"lint": "eslint react --ext .ts,.tsx --report-unused-disable-directives --max-warnings 0",
"lint-fix": "npm run lint -- --fix",
"prettier": "prettier --write",
"format": "npm run prettier -- . && npm run lint-fix"
},
"prettier": {
"singleQuote": true,
@@ -39,7 +41,6 @@
"eslint-plugin-react-refresh": "^0.4.4",
"eslint-plugin-simple-import-sort": "^10.0.0",
"prettier": "^3.0.3",
"sass": "^1.67.0",
"typescript": "^5.0.2",
"vite": "^4.4.5"
}

50
pyproject.toml Normal file
View File

@@ -0,0 +1,50 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.sdist]
include = ["python"]
[tool.hatch.build.targets.wheel]
packages = ["python/fastui"]
[tool.hatch.version]
path = "python/fastui/__init__.py"
[project]
name = "fastui"
description = "Build UIs fast."
authors = [{ name = "Samuel Colvin", email = "s@muelcolvin.com" }]
license = "MIT"
readme = "README.md"
classifiers = [
"Development Status :: 4 - Beta",
"Topic :: Internet",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
"Intended Audience :: Developers",
"Intended Audience :: Information Technology",
]
requires-python = ">=3.8"
dependencies = [
"pydantic>=2.5.0b1",
"fastapi>=0.104.0",
]
dynamic = ["version"]
[project.urls]
Homepage = "https://github.com/samuelcolvin/FastUI"
[tool.ruff]
line-length = 120
extend-select = ["Q", "RUF100", "UP", "I"]
flake8-quotes = {inline-quotes = "single", multiline-quotes = "double"}
format.quote-style="single"
target-version = "py38"

View File

@@ -0,0 +1,9 @@
__version__ = '0.0.1'
import pydantic
from .components import AnyComponent
class FastUI(pydantic.RootModel):
root: AnyComponent

View File

@@ -8,10 +8,12 @@ All CamelCase names in the namespace should be components.
from __future__ import annotations as _annotations
import typing
import pydantic
from . import extra, events
from .table import Table, Column, Display
from .. import events
from . import extra
from .table import Table
if typing.TYPE_CHECKING:
import pydantic.fields
@@ -32,6 +34,7 @@ class Page(pydantic.BaseModel):
"""
Similar to `container` in many UI frameworks, this should be a reasonable root component for most pages.
"""
children: list[AnyComponent]
class_name: extra.ClassName | None = None
type: typing.Literal['Page'] = 'Page'
@@ -77,6 +80,5 @@ PydanticModel = typing.TypeVar('PydanticModel', bound=pydantic.BaseModel)
AnyComponent = typing.Annotated[
Text | Div | Page | Heading | Row | Col | Button | Modal | Table,
pydantic.Field(discriminator='type')
Text | Div | Page | Heading | Row | Col | Button | Modal | Table, pydantic.Field(discriminator='type')
]

View File

@@ -1,4 +1,5 @@
from typing import Annotated
from pydantic import Field
ClassName = Annotated[str | list[str] | dict[str, bool | None], Field(serialization_alias='className')]

View File

@@ -1,39 +1,26 @@
from __future__ import annotations as _annotations
import typing
from enum import StrEnum
import pydantic
from . import extra, events
from .. import events
from ..display import Display
from . import extra
# TODO allow dataclasses and dicts here too
DataModel = typing.TypeVar('DataModel', bound=pydantic.BaseModel)
class Display(StrEnum):
"""
How to a value.
"""
auto = 'auto' # default, same as None below
plain = 'plain'
datetime = 'datetime'
date = 'date'
duration = 'duration'
as_title = 'as_title'
markdown = 'markdown'
json = 'json'
inline_code = 'inline_code'
class Column(pydantic.BaseModel):
"""
Description of a table column.
"""
field: str
display: Display | None = None
title: str | None = None
on_click: events.Event | None = pydantic.Field(None, serialization_alias='onClick')
on_click: typing.Annotated[events.Event | None, pydantic.Field(serialization_alias='onClick')] = None
class_name: extra.ClassName | None = None

17
python/fastui/display.py Normal file
View File

@@ -0,0 +1,17 @@
from enum import StrEnum
class Display(StrEnum):
"""
How to a value.
"""
auto = 'auto' # default, same as None below
plain = 'plain'
datetime = 'datetime'
date = 'date'
duration = 'duration'
as_title = 'as_title'
markdown = 'markdown'
json = 'json'
inline_code = 'inline_code'

View File

@@ -1,5 +1,6 @@
from typing import Annotated, Literal
from pydantic import Field, BaseModel
from pydantic import BaseModel, Field
class PageEvent(BaseModel):

View File

@@ -0,0 +1,3 @@
-r ./lint.txt
-r ./pyproject.txt
uvicorn[standard]

View File

@@ -0,0 +1,2 @@
ruff
pyright

View File

@@ -0,0 +1,15 @@
#
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# pip-compile --output-file=python/requirements/lint.txt --strip-extras python/requirements/lint.in
#
nodeenv==1.8.0
# via pyright
pyright==1.1.335
# via -r python/requirements/lint.in
ruff==0.1.5
# via -r python/requirements/lint.in
# The following packages are considered to be unsafe in a requirements file:
# setuptools

View File

@@ -0,0 +1,31 @@
#
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# pip-compile --output-file=python/requirements/pyproject.txt --strip-extras pyproject.toml
#
annotated-types==0.6.0
# via pydantic
anyio==3.7.1
# via
# fastapi
# starlette
fastapi==0.104.1
# via fastui (pyproject.toml)
idna==3.4
# via anyio
pydantic==2.5.0b1
# via
# fastapi
# fastui (pyproject.toml)
pydantic-core==2.14.1
# via pydantic
sniffio==1.3.0
# via anyio
starlette==0.27.0
# via fastapi
typing-extensions==4.8.0
# via
# fastapi
# pydantic
# pydantic-core

View File

@@ -15,7 +15,7 @@ export const HeadingComp: FC<HeadingProps> = (props) => {
return <HeadingComponent text={text} className={useClassNameGenerator(className, props)} />
}
function getComponent (level: 1 | 2 | 3 | 4 | 5 | 6): FC<{ text: string; className: string }> {
function getComponent(level: 1 | 2 | 3 | 4 | 5 | 6): FC<{ text: string; className: string }> {
switch (level) {
case 1:
return ({ text, className }) => <h1 className={className}>{text}</h1>

View File

@@ -81,7 +81,7 @@ interface WithChildren {
children: FastProps[]
}
function renderWithChildren<T extends WithChildren> (Component: FC<T>, props: T) {
function renderWithChildren<T extends WithChildren>(Component: FC<T>, props: T) {
const { children, ...rest } = props
// TODO is there a way to make this type safe?
return <Component {...(rest as any)}>{children}</Component>

View File

@@ -68,7 +68,7 @@ const request = async ({ url, method, headers, body }: Request): Promise<FastPro
return data as FastProps
}
export function FastUIController ({ rootUrl, pathSendMode, loading }: Props) {
export function FastUIController({ rootUrl, pathSendMode, loading }: Props) {
const [componentProps, setComponentProps] = useState<FastProps | null>(null)
const { fullPath } = useContext(LocationContext)

View File

@@ -14,7 +14,7 @@ export const ClassNameContext = createContext<ClassNameGenerator | null>(null)
* @param props The full props object sent from the backend, this is passed to the class name generator.
* @param dft default className to use if the class name generator is not set or returns undefined.
*/
export function useClassNameGenerator (classNameProp: ClassName, props: FastProps, dft?: ClassName): string {
export function useClassNameGenerator(classNameProp: ClassName, props: FastProps, dft?: ClassName): string {
const classNameGenerator = useContext(ClassNameContext)
if (combineClassNameProp(classNameProp)) {
if (!dft && classNameGenerator) {
@@ -33,7 +33,7 @@ export function useClassNameGenerator (classNameProp: ClassName, props: FastProp
* then we generate the default className and append the user's className to it.
* @param classNameProp
*/
function combineClassNameProp (classNameProp: ClassName): boolean {
function combineClassNameProp(classNameProp: ClassName): boolean {
if (Array.isArray(classNameProp)) {
// classNameProp is an array, check if it contains `+`
return classNameProp.some((c) => c === '+')
@@ -49,7 +49,7 @@ function combineClassNameProp (classNameProp: ClassName): boolean {
}
}
function combine (cn1: ClassName, cn2: ClassName): string {
function combine(cn1: ClassName, cn2: ClassName): string {
if (!cn1) {
return renderClassName(cn2)
} else if (!cn2) {
@@ -63,7 +63,7 @@ function combine (cn1: ClassName, cn2: ClassName): string {
* Renders the className to a string, removing plus signs.
* @param className
*/
export function renderClassName (className: ClassName): string {
export function renderClassName(className: ClassName): string {
if (typeof className === 'string') {
return className.replace(/^\+ /, '')
} else if (Array.isArray(className)) {

View File

@@ -30,7 +30,7 @@ const DefaultErrorDisplay: ErrorDisplayType = ({ title, description, children })
export const ErrorContext = createContext<ErrorContextType>({
error: null,
setError: () => null,
DisplayError: DefaultErrorDisplay
DisplayError: DefaultErrorDisplay,
})
const MaybeError: FC<{ children: ReactNode }> = ({ children }) => {
@@ -55,7 +55,7 @@ export const ErrorContextProvider: FC<Props> = ({ DisplayError, children }) => {
console.warn('setting error:', error)
setErrorState(error)
},
[setErrorState]
[setErrorState],
)
const contextValue: ErrorContextType = { error, setError, DisplayError: DisplayError ?? DefaultErrorDisplay }

View File

@@ -12,14 +12,14 @@ export interface GoToEvent {
url: string
}
function pageEventType (event: PageEvent): string {
function pageEventType(event: PageEvent): string {
return `fastui:${event.name}`
}
export function useFireEvent (): { fireEvent: (event?: PageEvent | GoToEvent) => void } {
export function useFireEvent(): { fireEvent: (event?: PageEvent | GoToEvent) => void } {
const location = useContext(LocationContext)
function fireEvent (event?: PageEvent | GoToEvent) {
function fireEvent(event?: PageEvent | GoToEvent) {
if (!event) {
return
}
@@ -38,7 +38,7 @@ export function useFireEvent (): { fireEvent: (event?: PageEvent | GoToEvent) =>
return { fireEvent }
}
export function useEventListenerToggle (event?: PageEvent, initialState = false): [boolean, () => void] {
export function useEventListenerToggle(event?: PageEvent, initialState = false): [boolean, () => void] {
const [state, setState] = useState(initialState)
const toggle = useCallback(() => setState((state) => !state), [])

View File

@@ -2,7 +2,7 @@ import { createContext, ReactNode, useEffect, useState, useCallback, useContext
import { ErrorContext } from './error'
function parseLocation (): string {
function parseLocation(): string {
const { href, origin } = window.location
// remove origin from the beginning of href
return href.slice(origin.length)
@@ -17,12 +17,12 @@ const initialPath = parseLocation()
const initialState = {
fullPath: initialPath,
goto: () => null
goto: () => null,
}
export const LocationContext = createContext<LocationState>(initialState)
export function LocationProvider ({ children }: { children: ReactNode }) {
export function LocationProvider({ children }: { children: ReactNode }) {
const [fullPath, setFullPath] = useState(initialPath)
const { setError } = useContext(ErrorContext)
@@ -66,8 +66,8 @@ export function LocationProvider ({ children }: { children: ReactNode }) {
setError(null)
setFullPath(newPath)
},
[setError]
)
[setError],
),
}
return <LocationContext.Provider value={value}>{children}</LocationContext.Provider>

View File

@@ -20,7 +20,7 @@ export interface FastUIProps {
customRender?: CustomRender
}
export function FastUI (props: FastUIProps) {
export function FastUI(props: FastUIProps) {
const { classNameGenerator, DisplayError, customRender, ...rest } = props
return (
<div className="fastui">

24
tsconfig.json Normal file
View File

@@ -0,0 +1,24 @@
{
"compilerOptions": {
"outDir": "./react-dist",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": false,
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["react"]
}