diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml
index aac67f7a6..f743b7cbd 100644
--- a/.github/workflows/benchmarks.yml
+++ b/.github/workflows/benchmarks.yml
@@ -80,7 +80,7 @@ jobs:
fail-fast: false
matrix:
# Show OS combos first in GUI
- os: [ubuntu-latest, windows-latest, macos-12]
+ os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ['3.9.18', '3.10.13', '3.11.5', '3.12.0']
exclude:
- os: windows-latest
@@ -92,7 +92,7 @@ jobs:
python-version: '3.9.18'
- os: macos-latest
python-version: '3.10.13'
- - os: macos-12
+ - os: macos-latest
python-version: '3.12.0'
include:
- os: windows-latest
@@ -155,7 +155,7 @@ jobs:
fail-fast: false
matrix:
# Show OS combos first in GUI
- os: [ubuntu-latest, windows-latest, macos-12]
+ os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ['3.11.5']
runs-on: ${{ matrix.os }}
diff --git a/.github/workflows/check_outdated_dependencies.yml b/.github/workflows/check_outdated_dependencies.yml
index fe8c42608..a7465defb 100644
--- a/.github/workflows/check_outdated_dependencies.yml
+++ b/.github/workflows/check_outdated_dependencies.yml
@@ -58,7 +58,7 @@ jobs:
working-directory: ./reflex-web
run: poetry run uv pip install -r requirements.txt
- name: Install additional dependencies for DB access
- run: poetry run uv pip install psycopg2-binary
+ run: poetry run uv pip install psycopg
- name: Init Website for reflex-web
working-directory: ./reflex-web
run: poetry run reflex init
diff --git a/.github/workflows/integration_app_harness.yml b/.github/workflows/integration_app_harness.yml
index 6ac5fe6ab..e6ea79377 100644
--- a/.github/workflows/integration_app_harness.yml
+++ b/.github/workflows/integration_app_harness.yml
@@ -22,9 +22,9 @@ jobs:
timeout-minutes: 30
strategy:
matrix:
- state_manager: ["redis", "memory"]
+ state_manager: ['redis', 'memory']
+ python-version: ['3.11.5', '3.12.0', '3.13.0']
split_index: [1, 2]
- python-version: ["3.11.5", "3.12.0"]
fail-fast: false
runs-on: ubuntu-22.04
services:
diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml
index 3e22234b8..b2304d463 100644
--- a/.github/workflows/integration_tests.yml
+++ b/.github/workflows/integration_tests.yml
@@ -43,7 +43,7 @@ jobs:
matrix:
# Show OS combos first in GUI
os: [ubuntu-latest, windows-latest]
- python-version: ['3.9.18', '3.10.13', '3.11.5', '3.12.0']
+ python-version: ['3.9.18', '3.10.13', '3.11.5', '3.12.0', '3.13.0']
exclude:
- os: windows-latest
python-version: '3.10.13'
@@ -73,7 +73,7 @@ jobs:
run: |
poetry run uv pip install -r requirements.txt
- name: Install additional dependencies for DB access
- run: poetry run uv pip install psycopg2-binary
+ run: poetry run uv pip install psycopg
- name: Check export --backend-only before init for counter example
working-directory: ./reflex-examples/counter
run: |
@@ -147,7 +147,7 @@ jobs:
working-directory: ./reflex-web
run: poetry run uv pip install $(grep -ivE "reflex " requirements.txt)
- name: Install additional dependencies for DB access
- run: poetry run uv pip install psycopg2-binary
+ run: poetry run uv pip install psycopg
- name: Init Website for reflex-web
working-directory: ./reflex-web
run: poetry run reflex init
@@ -198,7 +198,7 @@ jobs:
fail-fast: false
matrix:
python-version: ['3.11.5', '3.12.0']
- runs-on: macos-12
+ runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup_build_env
@@ -216,7 +216,7 @@ jobs:
working-directory: ./reflex-web
run: poetry run uv pip install -r requirements.txt
- name: Install additional dependencies for DB access
- run: poetry run uv pip install psycopg2-binary
+ run: poetry run uv pip install psycopg
- name: Init Website for reflex-web
working-directory: ./reflex-web
run: poetry run reflex init
diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml
index c76918583..25f5723f3 100644
--- a/.github/workflows/unit_tests.yml
+++ b/.github/workflows/unit_tests.yml
@@ -28,7 +28,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
- python-version: ['3.9.18', '3.10.13', '3.11.5', '3.12.0']
+ python-version: ['3.9.18', '3.10.13', '3.11.5', '3.12.0', '3.13.0']
# Windows is a bit behind on Python version availability in Github
exclude:
- os: windows-latest
@@ -88,8 +88,9 @@ jobs:
strategy:
fail-fast: false
matrix:
- python-version: ['3.9.18', '3.10.13', '3.11.5', '3.12.0']
- runs-on: macos-12
+ # Note: py39, py310 versions chosen due to available arm64 darwin builds.
+ python-version: ['3.9.13', '3.10.11', '3.11.5', '3.12.0', '3.13.0']
+ runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup_build_env
diff --git a/docker-example/production-app-platform/Dockerfile b/docker-example/production-app-platform/Dockerfile
index fec3b13f1..7bf71943b 100644
--- a/docker-example/production-app-platform/Dockerfile
+++ b/docker-example/production-app-platform/Dockerfile
@@ -52,7 +52,7 @@ FROM python:3.13-slim
WORKDIR /app
RUN adduser --disabled-password --home /app reflex
COPY --chown=reflex --from=init /app /app
-# Install libpq-dev for psycopg2 (skip if not using postgres).
+# Install libpq-dev for psycopg (skip if not using postgres).
RUN apt-get update -y && apt-get install -y libpq-dev && rm -rf /var/lib/apt/lists/*
USER reflex
ENV PATH="/app/.venv/bin:$PATH" PYTHONUNBUFFERED=1
diff --git a/docker-example/production-compose/Dockerfile b/docker-example/production-compose/Dockerfile
index 757c03b8e..9e69c1778 100644
--- a/docker-example/production-compose/Dockerfile
+++ b/docker-example/production-compose/Dockerfile
@@ -39,7 +39,7 @@ FROM python:3.13-slim
WORKDIR /app
RUN adduser --disabled-password --home /app reflex
COPY --chown=reflex --from=init /app /app
-# Install libpq-dev for psycopg2 (skip if not using postgres).
+# Install libpq-dev for psycopg (skip if not using postgres).
RUN apt-get update -y && apt-get install -y libpq-dev && rm -rf /var/lib/apt/lists/*
USER reflex
ENV PATH="/app/.venv/bin:$PATH" PYTHONUNBUFFERED=1
diff --git a/docker-example/production-compose/compose.prod.yaml b/docker-example/production-compose/compose.prod.yaml
index 225539515..7ed5b4eca 100644
--- a/docker-example/production-compose/compose.prod.yaml
+++ b/docker-example/production-compose/compose.prod.yaml
@@ -15,7 +15,7 @@ services:
app:
environment:
- DB_URL: postgresql+psycopg2://postgres:secret@db/postgres
+ DB_URL: postgresql+psycopg://postgres:secret@db/postgres
REDIS_URL: redis://redis:6379
depends_on:
- db
diff --git a/poetry.lock b/poetry.lock
index aa826e4b0..cc778d19b 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1542,18 +1542,18 @@ type = ["mypy (>=1.11.2)"]
[[package]]
name = "playwright"
-version = "1.49.0"
+version = "1.49.1"
description = "A high-level API to automate web browsers"
optional = false
python-versions = ">=3.9"
files = [
- {file = "playwright-1.49.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:704532a2d8ba580ec9e1895bfeafddce2e3d52320d4eb8aa38e80376acc5cbb0"},
- {file = "playwright-1.49.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e453f02c4e5cc2db7e9759c47e7425f32e50ac76c76b7eb17c69eed72f01c4d8"},
- {file = "playwright-1.49.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:37ae985309184472946a6eb1a237e5d93c9e58a781fa73b75c8751325002a5d4"},
- {file = "playwright-1.49.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:68d94beffb3c9213e3ceaafa66171affd9a5d9162e0c8a3eed1b1132c2e57598"},
- {file = "playwright-1.49.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f12d2aecdb41fc25a624cb15f3e8391c252ebd81985e3d5c1c261fe93779345"},
- {file = "playwright-1.49.0-py3-none-win32.whl", hash = "sha256:91103de52d470594ad375b512d7143fa95d6039111ae11a93eb4fe2f2b4a4858"},
- {file = "playwright-1.49.0-py3-none-win_amd64.whl", hash = "sha256:34d28a2c2d46403368610be4339898dc9c34eb9f7c578207b4715c49743a072a"},
+ {file = "playwright-1.49.1-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:1041ffb45a0d0bc44d698d3a5aa3ac4b67c9bd03540da43a0b70616ad52592b8"},
+ {file = "playwright-1.49.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9f38ed3d0c1f4e0a6d1c92e73dd9a61f8855133249d6f0cec28648d38a7137be"},
+ {file = "playwright-1.49.1-py3-none-macosx_11_0_universal2.whl", hash = "sha256:3be48c6d26dc819ca0a26567c1ae36a980a0303dcd4249feb6f59e115aaddfb8"},
+ {file = "playwright-1.49.1-py3-none-manylinux1_x86_64.whl", hash = "sha256:753ca90ee31b4b03d165cfd36e477309ebf2b4381953f2a982ff612d85b147d2"},
+ {file = "playwright-1.49.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd9bc8dab37aa25198a01f555f0a2e2c3813fe200fef018ac34dfe86b34994b9"},
+ {file = "playwright-1.49.1-py3-none-win32.whl", hash = "sha256:43b304be67f096058e587dac453ece550eff87b8fbed28de30f4f022cc1745bb"},
+ {file = "playwright-1.49.1-py3-none-win_amd64.whl", hash = "sha256:47b23cb346283278f5b4d1e1990bcb6d6302f80c0aa0ca93dd0601a1400191df"},
]
[package.dependencies]
diff --git a/pyproject.toml b/pyproject.toml
index 284342854..9dc89f929 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -92,8 +92,9 @@ build-backend = "poetry.core.masonry.api"
[tool.ruff]
target-version = "py39"
+output-format = "concise"
lint.isort.split-on-trailing-comma = false
-lint.select = ["B", "D", "E", "F", "I", "SIM", "W", "RUF", "FURB", "PERF"]
+lint.select = ["B", "D", "E", "F", "I", "SIM", "W", "RUF", "FURB", "PERF", "ERA"]
lint.ignore = ["B008", "D205", "E501", "F403", "SIM115", "RUF006", "RUF012"]
lint.pydocstyle.convention = "google"
diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js
index 622f171ad..608df084a 100644
--- a/reflex/.templates/web/utils/state.js
+++ b/reflex/.templates/web/utils/state.js
@@ -40,9 +40,6 @@ let event_processing = false;
// Array holding pending events to be processed.
const event_queue = [];
-// Pending upload promises, by id
-const upload_controllers = {};
-
/**
* Generate a UUID (Used for session tokens).
* Taken from: https://stackoverflow.com/questions/105034/how-do-i-create-a-guid-uuid
@@ -300,7 +297,7 @@ export const applyEvent = async (event, socket) => {
if (socket) {
socket.emit(
"event",
- JSON.stringify(event, (k, v) => (v === undefined ? null : v))
+ event,
);
return true;
}
@@ -407,6 +404,8 @@ export const connect = async (
transports: transports,
autoUnref: false,
});
+ // Ensure undefined fields in events are sent as null instead of removed
+ socket.current.io.encoder.replacer = (k, v) => (v === undefined ? null : v)
function checkVisibility() {
if (document.visibilityState === "visible") {
@@ -443,8 +442,7 @@ export const connect = async (
});
// On each received message, queue the updates and events.
- socket.current.on("event", async (message) => {
- const update = JSON5.parse(message);
+ socket.current.on("event", async (update) => {
for (const substate in update.delta) {
dispatch[substate](update.delta[substate]);
}
@@ -456,7 +454,7 @@ export const connect = async (
});
socket.current.on("reload", async (event) => {
event_processing = false;
- queueEvents([...initialEvents(), JSON5.parse(event)], socket);
+ queueEvents([...initialEvents(), event], socket);
});
document.addEventListener("visibilitychange", checkVisibility);
@@ -485,7 +483,9 @@ export const uploadFiles = async (
return false;
}
- if (upload_controllers[upload_id]) {
+ const upload_ref_name = `__upload_controllers_${upload_id}`
+
+ if (refs[upload_ref_name]) {
console.log("Upload already in progress for ", upload_id);
return false;
}
@@ -497,23 +497,31 @@ export const uploadFiles = async (
// Whenever called, responseText will contain the entire response so far.
const chunks = progressEvent.event.target.responseText.trim().split("\n");
// So only process _new_ chunks beyond resp_idx.
- chunks.slice(resp_idx).map((chunk) => {
- event_callbacks.map((f, ix) => {
- f(chunk)
- .then(() => {
- if (ix === event_callbacks.length - 1) {
- // Mark this chunk as processed.
- resp_idx += 1;
- }
- })
- .catch((e) => {
- if (progressEvent.progress === 1) {
- // Chunk may be incomplete, so only report errors when full response is available.
- console.log("Error parsing chunk", chunk, e);
- }
- return;
- });
- });
+ chunks.slice(resp_idx).map((chunk_json) => {
+ try {
+ const chunk = JSON5.parse(chunk_json);
+ event_callbacks.map((f, ix) => {
+ f(chunk)
+ .then(() => {
+ if (ix === event_callbacks.length - 1) {
+ // Mark this chunk as processed.
+ resp_idx += 1;
+ }
+ })
+ .catch((e) => {
+ if (progressEvent.progress === 1) {
+ // Chunk may be incomplete, so only report errors when full response is available.
+ console.log("Error processing chunk", chunk, e);
+ }
+ return;
+ });
+ });
+ } catch (e) {
+ if (progressEvent.progress === 1) {
+ console.log("Error parsing chunk", chunk_json, e);
+ }
+ return;
+ }
});
};
@@ -537,7 +545,7 @@ export const uploadFiles = async (
});
// Send the file to the server.
- upload_controllers[upload_id] = controller;
+ refs[upload_ref_name] = controller;
try {
return await axios.post(getBackendURL(UPLOADURL), formdata, config);
@@ -557,7 +565,7 @@ export const uploadFiles = async (
}
return false;
} finally {
- delete upload_controllers[upload_id];
+ delete refs[upload_ref_name];
}
};
@@ -799,7 +807,7 @@ export const useEventLoop = (
connect(
socket,
dispatch,
- ["websocket", "polling"],
+ ["websocket"],
setConnectErrors,
client_storage
);
diff --git a/reflex/__init__.py b/reflex/__init__.py
index 562524416..0be568b1a 100644
--- a/reflex/__init__.py
+++ b/reflex/__init__.py
@@ -331,7 +331,7 @@ _MAPPING: dict = {
"SessionStorage",
],
"middleware": ["middleware", "Middleware"],
- "model": ["session", "Model"],
+ "model": ["asession", "session", "Model"],
"state": [
"var",
"ComponentState",
diff --git a/reflex/__init__.pyi b/reflex/__init__.pyi
index 6f61435e6..6bdfa11a0 100644
--- a/reflex/__init__.pyi
+++ b/reflex/__init__.pyi
@@ -186,6 +186,7 @@ from .istate.wrappers import get_state as get_state
from .middleware import Middleware as Middleware
from .middleware import middleware as middleware
from .model import Model as Model
+from .model import asession as asession
from .model import session as session
from .page import page as page
from .state import ComponentState as ComponentState
diff --git a/reflex/app.py b/reflex/app.py
index cdf21aa35..10dd889b3 100644
--- a/reflex/app.py
+++ b/reflex/app.py
@@ -17,6 +17,7 @@ import sys
import traceback
from datetime import datetime
from pathlib import Path
+from types import SimpleNamespace
from typing import (
TYPE_CHECKING,
Any,
@@ -363,6 +364,11 @@ class App(MiddlewareMixin, LifespanMixin):
max_http_buffer_size=constants.POLLING_MAX_HTTP_BUFFER_SIZE,
ping_interval=constants.Ping.INTERVAL,
ping_timeout=constants.Ping.TIMEOUT,
+ json=SimpleNamespace(
+ dumps=staticmethod(format.json_dumps),
+ loads=staticmethod(json.loads),
+ ),
+ transports=["websocket"],
)
elif getattr(self.sio, "async_mode", "") != "asgi":
raise RuntimeError(
@@ -467,7 +473,7 @@ class App(MiddlewareMixin, LifespanMixin):
def add_page(
self,
- component: Component | ComponentCallable,
+ component: Component | ComponentCallable | None = None,
route: str | None = None,
title: str | Var | None = None,
description: str | Var | None = None,
@@ -490,17 +496,33 @@ class App(MiddlewareMixin, LifespanMixin):
meta: The metadata of the page.
Raises:
- ValueError: When the specified route name already exists.
+ PageValueError: When the component is not set for a non-404 page.
+ RouteValueError: When the specified route name already exists.
"""
# If the route is not set, get it from the callable.
if route is None:
if not isinstance(component, Callable):
- raise ValueError("Route must be set if component is not a callable.")
+ raise exceptions.RouteValueError(
+ "Route must be set if component is not a callable."
+ )
# Format the route.
route = format.format_route(component.__name__)
else:
route = format.format_route(route, format_case=False)
+ if route == constants.Page404.SLUG:
+ if component is None:
+ component = Default404Page.create()
+ component = wait_for_client_redirect(self._generate_component(component))
+ title = title or constants.Page404.TITLE
+ description = description or constants.Page404.DESCRIPTION
+ image = image or constants.Page404.IMAGE
+ else:
+ if component is None:
+ raise exceptions.PageValueError(
+ "Component must be set for a non-404 page."
+ )
+
# Check if the route given is valid
verify_route_validity(route)
@@ -516,7 +538,7 @@ class App(MiddlewareMixin, LifespanMixin):
if route == constants.PageNames.INDEX_ROUTE
else f"`{route}`"
)
- raise ValueError(
+ raise exceptions.RouteValueError(
f"Duplicate page route {route_name} already exists. Make sure you do not have two"
f" pages with the same route"
)
@@ -633,10 +655,14 @@ class App(MiddlewareMixin, LifespanMixin):
on_load: The event handler(s) that will be called each time the page load.
meta: The metadata of the page.
"""
- if component is None:
- component = Default404Page.create()
+ console.deprecate(
+ feature_name="App.add_custom_404_page",
+ reason=f"Use app.add_page(component, route='/{constants.Page404.SLUG}') instead.",
+ deprecation_version="0.6.7",
+ removal_version="0.8.0",
+ )
self.add_page(
- component=wait_for_client_redirect(self._generate_component(component)),
+ component=component,
route=constants.Page404.SLUG,
title=title or constants.Page404.TITLE,
image=image or constants.Page404.IMAGE,
@@ -837,7 +863,7 @@ class App(MiddlewareMixin, LifespanMixin):
# Render a default 404 page if the user didn't supply one
if constants.Page404.SLUG not in self.unevaluated_pages:
- self.add_custom_404_page()
+ self.add_page(route=constants.Page404.SLUG)
# Fix up the style.
self.style = evaluate_style_namespaces(self.style)
@@ -947,12 +973,12 @@ class App(MiddlewareMixin, LifespanMixin):
is not None
):
executor = concurrent.futures.ProcessPoolExecutor(
- max_workers=number_of_processes,
+ max_workers=number_of_processes or None,
mp_context=multiprocessing.get_context("fork"),
)
else:
executor = concurrent.futures.ThreadPoolExecutor(
- max_workers=environment.REFLEX_COMPILE_THREADS.get()
+ max_workers=environment.REFLEX_COMPILE_THREADS.get() or None
)
for route, component in zip(self.pages, page_components):
@@ -965,7 +991,6 @@ class App(MiddlewareMixin, LifespanMixin):
def _submit_work(fn, *args, **kwargs):
f = executor.submit(fn, *args, **kwargs)
- # f = executor.apipe(fn, *args, **kwargs)
result_futures.append(f)
# Compile the pre-compiled pages.
@@ -1157,7 +1182,7 @@ class App(MiddlewareMixin, LifespanMixin):
if hasattr(handler_fn, "__name__"):
_fn_name = handler_fn.__name__
else:
- _fn_name = handler_fn.__class__.__name__
+ _fn_name = type(handler_fn).__name__
if isinstance(handler_fn, functools.partial):
raise ValueError(
@@ -1270,7 +1295,7 @@ async def process(
await asyncio.create_task(
app.event_namespace.emit(
"reload",
- data=format.json_dumps(event),
+ data=event,
to=sid,
)
)
@@ -1523,7 +1548,7 @@ class EventNamespace(AsyncNamespace):
"""
# Creating a task prevents the update from being blocked behind other coroutines.
await asyncio.create_task(
- self.emit(str(constants.SocketEvent.EVENT), update.json(), to=sid)
+ self.emit(str(constants.SocketEvent.EVENT), update, to=sid)
)
async def on_event(self, sid, data):
@@ -1536,7 +1561,7 @@ class EventNamespace(AsyncNamespace):
sid: The Socket.IO session id.
data: The event data.
"""
- fields = json.loads(data)
+ fields = data
# Get the event.
event = Event(
**{k: v for k, v in fields.items() if k not in ("handler", "event_actions")}
diff --git a/reflex/assets.py b/reflex/assets.py
index 8a50664b6..a9aa7a6a9 100644
--- a/reflex/assets.py
+++ b/reflex/assets.py
@@ -5,7 +5,7 @@ from pathlib import Path
from typing import Optional
from reflex import constants
-from reflex.utils.exec import is_backend_only
+from reflex.config import EnvironmentVariables
def asset(
@@ -52,7 +52,7 @@ def asset(
The relative URL to the asset.
"""
assets = constants.Dirs.APP_ASSETS
- backend_only = is_backend_only()
+ backend_only = EnvironmentVariables.REFLEX_BACKEND_ONLY.get()
# Local asset handling
if not shared:
diff --git a/reflex/components/component.py b/reflex/components/component.py
index 2df1c28f3..9b74f4925 100644
--- a/reflex/components/component.py
+++ b/reflex/components/component.py
@@ -161,7 +161,7 @@ class ComponentNamespace(SimpleNamespace):
Returns:
The hash of the namespace.
"""
- return hash(self.__class__.__name__)
+ return hash(type(self).__name__)
def evaluate_style_namespaces(style: ComponentStyle) -> dict:
@@ -653,7 +653,6 @@ class Component(BaseComponent, ABC):
Returns:
The event triggers.
-
"""
default_triggers: Dict[str, types.ArgsSpec | Sequence[types.ArgsSpec]] = {
EventTriggers.ON_FOCUS: no_args_event_spec,
@@ -2566,7 +2565,7 @@ class LiteralComponentVar(CachedVarOperation, LiteralVar, ComponentVar):
Returns:
The hash of the var.
"""
- return hash((self.__class__.__name__, self._js_expr))
+ return hash((type(self).__name__, self._js_expr))
@classmethod
def create(
diff --git a/reflex/components/core/banner.pyi b/reflex/components/core/banner.pyi
index 3296b84ee..f44ee7992 100644
--- a/reflex/components/core/banner.pyi
+++ b/reflex/components/core/banner.pyi
@@ -321,7 +321,7 @@ class ConnectionPulser(Div):
"""Create a connection pulser component.
Args:
- access_key: Provides a hint for generating a keyboard shortcut for the current element.
+ access_key: Provides a hint for generating a keyboard shortcut for the current element.
auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
content_editable: Indicates whether the element's content is editable.
context_menu: Defines the ID of a