sinho 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/.github/workflows/ci.yml +24 -0
  2. package/.github/workflows/deploy-docs.yml +47 -0
  3. package/.prettierrc +3 -0
  4. package/LICENSE.md +21 -0
  5. package/README.md +33 -0
  6. package/ci/check-size.js +8 -0
  7. package/dist/array_mutation.d.ts +16 -0
  8. package/dist/array_mutation.js +75 -0
  9. package/dist/array_mutation.js.map +1 -0
  10. package/dist/bundle.d.ts +1126 -0
  11. package/dist/bundle.js +1074 -0
  12. package/dist/bundle.min.js +1 -0
  13. package/dist/component.d.ts +253 -0
  14. package/dist/component.js +256 -0
  15. package/dist/component.js.map +1 -0
  16. package/dist/context.d.ts +21 -0
  17. package/dist/context.js +34 -0
  18. package/dist/context.js.map +1 -0
  19. package/dist/create_element.d.ts +43 -0
  20. package/dist/create_element.js +43 -0
  21. package/dist/create_element.js.map +1 -0
  22. package/dist/dom.d.ts +602 -0
  23. package/dist/dom.js +97 -0
  24. package/dist/dom.js.map +1 -0
  25. package/dist/intrinsic/ClassComponent.d.ts +2 -0
  26. package/dist/intrinsic/ClassComponent.js +10 -0
  27. package/dist/intrinsic/ClassComponent.js.map +1 -0
  28. package/dist/intrinsic/Dynamic.d.ts +33 -0
  29. package/dist/intrinsic/Dynamic.js +53 -0
  30. package/dist/intrinsic/Dynamic.js.map +1 -0
  31. package/dist/intrinsic/ErrorBoundary.d.ts +14 -0
  32. package/dist/intrinsic/ErrorBoundary.js +36 -0
  33. package/dist/intrinsic/ErrorBoundary.js.map +1 -0
  34. package/dist/intrinsic/For.d.ts +10 -0
  35. package/dist/intrinsic/For.js +81 -0
  36. package/dist/intrinsic/For.js.map +1 -0
  37. package/dist/intrinsic/Fragment.d.ts +23 -0
  38. package/dist/intrinsic/Fragment.js +28 -0
  39. package/dist/intrinsic/Fragment.js.map +1 -0
  40. package/dist/intrinsic/If.d.ts +24 -0
  41. package/dist/intrinsic/If.js +47 -0
  42. package/dist/intrinsic/If.js.map +1 -0
  43. package/dist/intrinsic/Portal.d.ts +6 -0
  44. package/dist/intrinsic/Portal.js +15 -0
  45. package/dist/intrinsic/Portal.js.map +1 -0
  46. package/dist/intrinsic/Style.d.ts +7 -0
  47. package/dist/intrinsic/Style.js +70 -0
  48. package/dist/intrinsic/Style.js.map +1 -0
  49. package/dist/intrinsic/TagComponent.d.ts +4 -0
  50. package/dist/intrinsic/TagComponent.js +67 -0
  51. package/dist/intrinsic/TagComponent.js.map +1 -0
  52. package/dist/intrinsic/Text.d.ts +6 -0
  53. package/dist/intrinsic/Text.js +16 -0
  54. package/dist/intrinsic/Text.js.map +1 -0
  55. package/dist/intrinsic/mod.d.ts +5 -0
  56. package/dist/intrinsic/mod.js +6 -0
  57. package/dist/intrinsic/mod.js.map +1 -0
  58. package/dist/jsx-runtime/mod.d.ts +23 -0
  59. package/dist/jsx-runtime/mod.js +11 -0
  60. package/dist/jsx-runtime/mod.js.map +1 -0
  61. package/dist/mod.d.ts +8 -0
  62. package/dist/mod.js +7 -0
  63. package/dist/mod.js.map +1 -0
  64. package/dist/renderer.d.ts +13 -0
  65. package/dist/renderer.js +25 -0
  66. package/dist/renderer.js.map +1 -0
  67. package/dist/scope.d.ts +138 -0
  68. package/dist/scope.js +228 -0
  69. package/dist/scope.js.map +1 -0
  70. package/dist/template.d.ts +10 -0
  71. package/dist/template.js +7 -0
  72. package/dist/template.js.map +1 -0
  73. package/dist/utils.d.ts +6 -0
  74. package/dist/utils.js +13 -0
  75. package/dist/utils.js.map +1 -0
  76. package/package.json +71 -0
  77. package/src/array_mutation.ts +118 -0
  78. package/src/component.ts +624 -0
  79. package/src/context.ts +70 -0
  80. package/src/create_element.ts +89 -0
  81. package/src/dom.ts +819 -0
  82. package/src/intrinsic/ClassComponent.ts +17 -0
  83. package/src/intrinsic/For.ts +122 -0
  84. package/src/intrinsic/Fragment.ts +38 -0
  85. package/src/intrinsic/If.ts +73 -0
  86. package/src/intrinsic/Portal.ts +25 -0
  87. package/src/intrinsic/Style.ts +120 -0
  88. package/src/intrinsic/TagComponent.ts +102 -0
  89. package/src/intrinsic/Text.ts +24 -0
  90. package/src/intrinsic/mod.ts +5 -0
  91. package/src/jsx-runtime/mod.ts +41 -0
  92. package/src/mod.ts +37 -0
  93. package/src/renderer.ts +45 -0
  94. package/src/scope.ts +404 -0
  95. package/src/template.ts +16 -0
  96. package/src/utils.ts +29 -0
  97. package/terser.config.json +16 -0
  98. package/tsconfig.json +18 -0
  99. package/web/README.md +41 -0
  100. package/web/babel.config.js +3 -0
  101. package/web/dist/shingo.min.d.ts +1131 -0
  102. package/web/dist/shingo.min.js +1 -0
  103. package/web/docusaurus.config.ts +151 -0
  104. package/web/package-lock.json +14850 -0
  105. package/web/package.json +54 -0
  106. package/web/sidebars.ts +31 -0
  107. package/web/src/components/monacoEditor.tsx +72 -0
  108. package/web/src/components/playground.tsx +89 -0
  109. package/web/src/components/playgroundComponent.tsx +168 -0
  110. package/web/src/css/custom.css +37 -0
  111. package/web/src/pages/index.module.css +31 -0
  112. package/web/src/pages/index.tsx +73 -0
  113. package/web/src/pages/playground.tsx +64 -0
  114. package/web/static/.nojekyll +0 -0
  115. package/web/static/dist/bundle.d.ts +1126 -0
  116. package/web/static/dist/bundle.min.js +1 -0
  117. package/web/tsconfig.json +8 -0
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "site",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "docusaurus": "docusaurus",
7
+ "prepare-static": "copyfiles --flat ../dist/bundle.min.js ../dist/bundle.d.ts ./static/dist",
8
+ "start": "npm run prepare-static && docusaurus start",
9
+ "build": "npm run prepare-static && docusaurus build",
10
+ "swizzle": "docusaurus swizzle",
11
+ "deploy": "docusaurus deploy",
12
+ "clear": "docusaurus clear",
13
+ "serve": "docusaurus serve",
14
+ "write-translations": "docusaurus write-translations",
15
+ "write-heading-ids": "docusaurus write-heading-ids",
16
+ "typecheck": "tsc"
17
+ },
18
+ "dependencies": {
19
+ "@docusaurus/core": "3.2.1",
20
+ "@docusaurus/preset-classic": "3.2.1",
21
+ "@mdx-js/react": "^3.0.0",
22
+ "@swc/wasm-web": "^1.4.17",
23
+ "clsx": "^2.0.0",
24
+ "monaco-editor": "^0.48.0",
25
+ "monaco-editor-webpack-plugin": "^7.1.0",
26
+ "prism-react-renderer": "^2.3.0",
27
+ "react": "^18.0.0",
28
+ "react-dom": "^18.0.0",
29
+ "sinho": "file:.."
30
+ },
31
+ "devDependencies": {
32
+ "@docusaurus/module-type-aliases": "3.2.1",
33
+ "@docusaurus/tsconfig": "3.2.1",
34
+ "@docusaurus/types": "3.2.1",
35
+ "docusaurus-plugin-typedoc-api": "^4.2.0",
36
+ "typedoc": "^0.25.13",
37
+ "typescript": "^5.4.3"
38
+ },
39
+ "browserslist": {
40
+ "production": [
41
+ ">0.5%",
42
+ "not dead",
43
+ "not op_mini all"
44
+ ],
45
+ "development": [
46
+ "last 3 chrome version",
47
+ "last 3 firefox version",
48
+ "last 5 safari version"
49
+ ]
50
+ },
51
+ "engines": {
52
+ "node": ">=18.0"
53
+ }
54
+ }
@@ -0,0 +1,31 @@
1
+ import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
2
+
3
+ /**
4
+ * Creating a sidebar enables you to:
5
+ - create an ordered group of docs
6
+ - render a sidebar for each doc of that group
7
+ - provide next/previous navigation
8
+
9
+ The sidebars can be generated from the filesystem, or explicitly defined here.
10
+
11
+ Create as many sidebars as you want.
12
+ */
13
+ const sidebars: SidebarsConfig = {
14
+ // By default, Docusaurus generates a sidebar from the docs folder structure
15
+ tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
16
+
17
+ // But you can create a sidebar manually
18
+ /*
19
+ tutorialSidebar: [
20
+ 'intro',
21
+ 'hello',
22
+ {
23
+ type: 'category',
24
+ label: 'Tutorial',
25
+ items: ['tutorial-basics/create-a-document'],
26
+ },
27
+ ],
28
+ */
29
+ };
30
+
31
+ export default sidebars;
@@ -0,0 +1,72 @@
1
+ import { useColorMode } from "@docusaurus/theme-common";
2
+ import useBaseUrl from "@docusaurus/useBaseUrl";
3
+ import * as monaco from "monaco-editor";
4
+ import { CSSProperties, FC, useEffect, useRef } from "react";
5
+
6
+ export const MonacoEditor: FC<{
7
+ style?: CSSProperties;
8
+ text?: string;
9
+ onChange?: (text: string) => void;
10
+ }> = (props) => {
11
+ const divRef = useRef<HTMLDivElement>(null);
12
+ const typesPath = useBaseUrl("/dist/bundle.d.ts");
13
+ const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>();
14
+ const { colorMode } = useColorMode();
15
+
16
+ useEffect(() => {
17
+ (async () => {
18
+ editorRef.current = monaco.editor.create(divRef.current!, {
19
+ model: monaco.editor.createModel(
20
+ props.text!,
21
+ "typescript",
22
+ monaco.Uri.file("/main.tsx"),
23
+ ),
24
+ language: "typescript",
25
+ minimap: {
26
+ enabled: false,
27
+ },
28
+ smoothScrolling: true,
29
+ automaticLayout: true,
30
+ });
31
+
32
+ editorRef.current.onDidChangeModelContent(() => {
33
+ props.onChange?.(editorRef.current!.getValue());
34
+ });
35
+
36
+ monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
37
+ strict: true,
38
+ jsx: monaco.languages.typescript.JsxEmit.ReactJSX,
39
+ jsxImportSource: "sinho",
40
+ });
41
+
42
+ const types = await fetch(typesPath).then((res) => res.text());
43
+
44
+ monaco.languages.typescript.typescriptDefaults.addExtraLib(
45
+ `declare module "sinho" {\n${types}\n}`,
46
+ "ts:sinho.d.ts",
47
+ );
48
+
49
+ monaco.languages.typescript.typescriptDefaults.addExtraLib(
50
+ `declare module "sinho/jsx-runtime" {\n${types}\n}`,
51
+ "ts:sinho-jsx-runtime.d.ts",
52
+ );
53
+ })();
54
+
55
+ return () => {
56
+ editorRef.current?.getModel()!.dispose();
57
+ editorRef.current?.dispose();
58
+ };
59
+ }, []);
60
+
61
+ useEffect(() => {
62
+ monaco.editor.setTheme(colorMode == "light" ? "vs" : "vs-dark");
63
+ }, [colorMode]);
64
+
65
+ useEffect(() => {
66
+ if (editorRef.current?.getValue() != props.text) {
67
+ editorRef.current?.setValue(props.text!);
68
+ }
69
+ }, [props.text]);
70
+
71
+ return <div ref={divRef} style={props.style} />;
72
+ };
@@ -0,0 +1,89 @@
1
+ import { useColorMode } from "@docusaurus/theme-common";
2
+ import useBaseUrl from "@docusaurus/useBaseUrl";
3
+ import {
4
+ CSSProperties,
5
+ FC,
6
+ RefObject,
7
+ useEffect,
8
+ useRef,
9
+ useState,
10
+ } from "react";
11
+
12
+ export const Playground: FC<{
13
+ innerRef?: RefObject<HTMLElement>;
14
+ style?: CSSProperties;
15
+ headerText?: string;
16
+ customCode?: string;
17
+ autosize?: boolean;
18
+ }> = (props) => {
19
+ const { colorMode } = useColorMode();
20
+ const sinhoPath = useBaseUrl("/dist/bundle.min.js");
21
+ const importMap = {
22
+ imports: {
23
+ sinho: sinhoPath,
24
+ "sinho/jsx-runtime": sinhoPath,
25
+ },
26
+ };
27
+
28
+ useEffect(() => {
29
+ import("./playgroundComponent");
30
+ }, []);
31
+
32
+ const Playground: any = "x-playground";
33
+
34
+ return (
35
+ <Playground
36
+ ref={props.innerRef}
37
+ style={props.style}
38
+ color-mode={colorMode}
39
+ header-text={props.headerText}
40
+ autosize={props.autosize}
41
+ import-map={JSON.stringify(importMap)}
42
+ custom-code={props.customCode}
43
+ />
44
+ );
45
+ };
46
+
47
+ export const CodeSnippetPlayground: FC<{
48
+ headerText?: string;
49
+ customCode?: (code: string) => string;
50
+ }> = (props) => {
51
+ const [customCode, setCustomCode] = useState("");
52
+ const playgroundRef = useRef<HTMLElement>(null);
53
+
54
+ useEffect(() => {
55
+ if (playgroundRef.current == null) return;
56
+
57
+ const codeElement = playgroundRef.current
58
+ .previousElementSibling as HTMLElement | null;
59
+ const detectedCode = codeElement?.innerText ?? "";
60
+
61
+ setCustomCode(props.customCode?.(detectedCode) ?? detectedCode);
62
+ }, [props.customCode]);
63
+
64
+ return (
65
+ <Playground
66
+ innerRef={playgroundRef}
67
+ autosize
68
+ headerText={props.headerText}
69
+ customCode={customCode}
70
+ />
71
+ );
72
+ };
73
+
74
+ export const CodeSnippetComponentPlayground: FC<{
75
+ headerText?: string;
76
+ componentName: string;
77
+ }> = (props) => {
78
+ return (
79
+ <CodeSnippetPlayground
80
+ headerText={props.headerText}
81
+ customCode={(code) =>
82
+ `${code}
83
+ import { defineComponents } from "sinho";
84
+ defineComponents(${props.componentName});
85
+ document.body.append(new ${props.componentName}());`
86
+ }
87
+ />
88
+ );
89
+ };
@@ -0,0 +1,168 @@
1
+ /** @jsxImportSource sinho */
2
+
3
+ import {
4
+ Component,
5
+ prop,
6
+ Style,
7
+ css,
8
+ defineComponents,
9
+ useSignal,
10
+ useEffect,
11
+ MaybeSignal,
12
+ } from "sinho";
13
+
14
+ export class Playground extends Component("x-playground", {
15
+ colorMode: prop<"light" | "dark">("light", {
16
+ attribute: (value) => (value === "dark" ? value : "light"),
17
+ }),
18
+ headerText: prop<string>("Preview", {
19
+ attribute: String,
20
+ }),
21
+ importMap: prop<{ imports: Record<string, string> }>(
22
+ { imports: {} },
23
+ { attribute: JSON.parse },
24
+ ),
25
+ customCode: prop<string>("", {
26
+ attribute: String,
27
+ }),
28
+ autosize: prop<boolean>(false, {
29
+ attribute: () => true,
30
+ }),
31
+ }) {
32
+ render() {
33
+ const [src, setSrc] = useSignal(
34
+ "document.body.innerHTML = '<p>Loadingâ€Ļ</p>';",
35
+ );
36
+ const [error, setError] = useSignal<Error>();
37
+
38
+ useEffect(() => {
39
+ setError(undefined);
40
+ const customCode = this.props.customCode();
41
+
42
+ (async () => {
43
+ try {
44
+ const swc = await import("@swc/wasm-web");
45
+ await swc.default();
46
+
47
+ const { code } = swc.transformSync(customCode, {
48
+ jsc: {
49
+ target: "es2022",
50
+ parser: {
51
+ syntax: "typescript",
52
+ tsx: true,
53
+ },
54
+ transform: {
55
+ react: {
56
+ runtime: "automatic",
57
+ importSource: "sinho",
58
+ },
59
+ },
60
+ },
61
+ });
62
+
63
+ setSrc(code);
64
+ } catch (err) {
65
+ setError(new Error((err as string).replace(/\x1B\[.*?m/g, "")));
66
+ }
67
+ })();
68
+ });
69
+
70
+ const iframeCss = css`
71
+ @import url("https://rsms.me/inter/inter.css");
72
+
73
+ body {
74
+ background: ${() =>
75
+ this.props.colorMode() == "light" ? "#f6f8fa" : "#282a36"};
76
+ color: ${() =>
77
+ this.props.colorMode() == "light" ? "#1c1e21" : "#e3e3e3"};
78
+ font-family: "Inter", sans-serif;
79
+ margin: 0;
80
+ padding: 0.5em 1em;
81
+ overflow: ${() => (this.props.autosize() ? "hidden" : "auto")};
82
+ }
83
+ `;
84
+
85
+ const jsBlob = () =>
86
+ new Blob(
87
+ [
88
+ error() == null
89
+ ? src()
90
+ : `\
91
+ document.body.innerHTML = '<p>Error loading preview:</p><pre id="error"></pre>';
92
+ document.getElementById("error").innerText = ${JSON.stringify(
93
+ error()!.message,
94
+ )};`,
95
+ ],
96
+ { type: "application/javascript" },
97
+ );
98
+
99
+ const htmlBlob = () =>
100
+ new Blob(
101
+ [
102
+ `<!DOCTYPE html>
103
+ <html>
104
+ <head>
105
+ <base href="${location.href}" />
106
+ <script type="importmap">${JSON.stringify(this.props.importMap())}</script>
107
+ <style>${MaybeSignal.get(iframeCss)}</style>
108
+ <script type="module" src="${URL.createObjectURL(jsBlob())}"></script>
109
+ </head>
110
+ <body>
111
+ </body>
112
+ </html>`,
113
+ ],
114
+ { type: "text/html" },
115
+ );
116
+
117
+ return (
118
+ <>
119
+ <div class="header">{this.props.headerText}</div>
120
+
121
+ <iframe
122
+ src={() => URL.createObjectURL(htmlBlob())}
123
+ onload={(evt) => {
124
+ if (!this.props.autosize()) return;
125
+
126
+ const bodySize =
127
+ evt.currentTarget.contentWindow!.document.body.getBoundingClientRect();
128
+
129
+ evt.currentTarget.height = bodySize.height.toString();
130
+ }}
131
+ />
132
+
133
+ <Style>{css`
134
+ :host {
135
+ display: flex;
136
+ flex-direction: column;
137
+ position: relative;
138
+ margin-bottom: var(--ifm-leading);
139
+ }
140
+
141
+ .header {
142
+ border-bottom: 1px solid var(--ifm-color-emphasis-300);
143
+ font-size: var(--ifm-code-font-size);
144
+ font-weight: 500;
145
+ padding: 0.75rem var(--ifm-pre-padding);
146
+ border-top-left-radius: var(--ifm-code-border-radius);
147
+ border-top-right-radius: var(--ifm-code-border-radius);
148
+ background: ${() =>
149
+ this.props.colorMode() == "light" ? "#f6f8fa" : "#282a36"};
150
+ }
151
+
152
+ iframe {
153
+ flex: ${this.props.autosize() ? "auto" : "1"};
154
+ display: block;
155
+ border: none;
156
+ width: 100%;
157
+ }
158
+ `}</Style>
159
+ </>
160
+ );
161
+ }
162
+ }
163
+
164
+ try {
165
+ defineComponents(Playground);
166
+ } catch (err) {
167
+ console.error(err);
168
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Any CSS included here will be global. The classic template
3
+ * bundles Infima by default. Infima is a CSS framework designed to
4
+ * work well for content-centric websites.
5
+ */
6
+
7
+ /* You can override the default Infima variables here. */
8
+ :root {
9
+ --ifm-font-family-base: Inter, sans-serif;
10
+ font-feature-settings:
11
+ "liga" 1,
12
+ "calt" 1; /* fix for Chrome */
13
+
14
+ --ifm-color-primary-darkest: #93160f;
15
+ --ifm-color-primary-darker: #c2221a;
16
+ --ifm-color-primary-dark: #e24627;
17
+ --ifm-color-primary: #f06533;
18
+ --ifm-color-primary-light: #ef9540;
19
+ --ifm-color-primary-lighter: #ffb655;
20
+ --ifm-color-primary-lightest: #ffcf6f;
21
+ --ifm-code-font-size: 90%;
22
+ --docusaurus-highlighted-code-line-bg: rgba(255, 207, 111, 0.2);
23
+ }
24
+
25
+ /* For readability concerns, you should choose a lighter palette in dark mode. */
26
+ [data-theme="dark"] {
27
+ --docusaurus-highlighted-code-line-bg: rgba(255, 207, 111, 0.1);
28
+ }
29
+
30
+ h1,
31
+ h2,
32
+ h3,
33
+ h4,
34
+ h5,
35
+ h6 {
36
+ letter-spacing: -0.02em;
37
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * CSS files with the .module.css suffix will be treated as CSS modules
3
+ * and scoped locally.
4
+ */
5
+
6
+ .heroBanner {
7
+ padding: 4rem 0;
8
+ text-align: center;
9
+ position: relative;
10
+ overflow: hidden;
11
+ }
12
+
13
+ @media screen and (max-width: 996px) {
14
+ .heroBanner {
15
+ padding: 2rem;
16
+ }
17
+ }
18
+
19
+ .buttons {
20
+ display: flex;
21
+ align-items: center;
22
+ justify-content: center;
23
+ gap: .7em;
24
+ }
25
+
26
+ main {
27
+ margin: 1.5em auto;
28
+ display: inline-block;
29
+ max-width: 100%;
30
+ padding: 0 var(--ifm-spacing-horizontal);
31
+ }
@@ -0,0 +1,73 @@
1
+ import clsx from "clsx";
2
+ import Link from "@docusaurus/Link";
3
+ import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
4
+ import Layout from "@theme/Layout";
5
+ import Heading from "@theme/Heading";
6
+
7
+ import { CodeSnippetComponentPlayground } from "../components/playground";
8
+ import styles from "./index.module.css";
9
+ import CodeBlock from "@theme/CodeBlock";
10
+
11
+ function HomepageHeader() {
12
+ const { siteConfig } = useDocusaurusContext();
13
+ return (
14
+ <header className={clsx("hero", styles.heroBanner)}>
15
+ <div className="container">
16
+ <Heading as="h1" className="hero__title">
17
+ {siteConfig.title}
18
+ </Heading>
19
+ <p className="hero__subtitle">{siteConfig.tagline}</p>
20
+ <div className={styles.buttons}>
21
+ <Link
22
+ className="button button--primary button--lg"
23
+ to="/docs/installation"
24
+ >
25
+ Documentation
26
+ </Link>
27
+ <Link className="button button--secondary button--lg" to="/api">
28
+ API
29
+ </Link>
30
+ </div>
31
+ </div>
32
+ </header>
33
+ );
34
+ }
35
+
36
+ export default function Home(): JSX.Element {
37
+ return (
38
+ <Layout description="A lightweight signal-based library for building web components with a React-like API.">
39
+ <HomepageHeader />
40
+ <main>
41
+ <ul>
42
+ <li>🌌 Web standards with custom HTML elements</li>
43
+ <li>âš›ī¸ React-like API</li>
44
+ <li>âœ’ī¸ Declarative templating with JSX (no additional parsing)</li>
45
+ <li>đŸšĨ Fine-grained reactivity with signals</li>
46
+ <li>🛟 Type-safe components out of the box with TypeScript</li>
47
+ <li>đŸĒļ Lightweight (~4KB minified and compressed)</li>
48
+ </ul>
49
+
50
+ <CodeBlock language="tsx">{`\
51
+ import { Component, useSignal } from "sinho";
52
+
53
+ class Counter extends Component("x-counter") {
54
+ render() {
55
+ const [value, setValue] = useSignal(0);
56
+
57
+ return (
58
+ <>
59
+ <p>Counter: {value}</p>
60
+ <p>
61
+ <button onclick={() => setValue((n) => n + 1)}>Increment</button>{" "}
62
+ <button onclick={() => setValue((n) => n - 1)}>Decrement</button>
63
+ </p>
64
+ </>
65
+ );
66
+ }
67
+ }`}</CodeBlock>
68
+
69
+ <CodeSnippetComponentPlayground componentName="Counter" />
70
+ </main>
71
+ </Layout>
72
+ );
73
+ }
@@ -0,0 +1,64 @@
1
+ import Layout from "@theme/Layout";
2
+ import { Playground } from "../components/playground";
3
+ import { useState } from "react";
4
+ import BrowserOnly from "@docusaurus/BrowserOnly";
5
+
6
+ export default function PlaygroundPage() {
7
+ const [src, setSrc] = useState(`\
8
+ import { Component, useSignal, defineComponents } from "sinho";
9
+
10
+ class Counter extends Component("x-counter") {
11
+ render() {
12
+ const [value, setValue] = useSignal(0);
13
+
14
+ return (
15
+ <>
16
+ <p>Counter: {value}</p>
17
+ <p>
18
+ <button onclick={() => setValue((n) => n + 1)}>Increment</button>{" "}
19
+ <button onclick={() => setValue((n) => n - 1)}>Decrement</button>
20
+ </p>
21
+ </>
22
+ );
23
+ }
24
+ }
25
+
26
+ defineComponents(Counter);
27
+ document.body.append(new Counter());`);
28
+
29
+ return (
30
+ <Layout
31
+ title="Playground"
32
+ description="A lightweight signal-based library for building web components with a React-like API."
33
+ noFooter
34
+ >
35
+ <div
36
+ style={{
37
+ alignSelf: "stretch",
38
+ flex: 1,
39
+ display: "flex",
40
+ alignItems: "stretch",
41
+ }}
42
+ >
43
+ <BrowserOnly>
44
+ {() => {
45
+ const { MonacoEditor } =
46
+ require("../components/monacoEditor") as typeof import("../components/monacoEditor");
47
+
48
+ return (
49
+ <MonacoEditor
50
+ style={{ flex: 1, overflow: "hidden" }}
51
+ text={src}
52
+ onChange={(src) => setSrc(src)}
53
+ />
54
+ );
55
+ }}
56
+ </BrowserOnly>
57
+ <Playground
58
+ style={{ flex: 1, marginBottom: 0, overflow: "hidden" }}
59
+ customCode={src}
60
+ />
61
+ </div>
62
+ </Layout>
63
+ );
64
+ }
File without changes