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
package/src/scope.ts ADDED
@@ -0,0 +1,404 @@
1
+ /**
2
+ * Represents a signal-based computation.
3
+ */
4
+ export interface SignalLike<out T> {
5
+ /**
6
+ * Gets the current value of the signal with tracking by default.
7
+ */
8
+ (): T;
9
+ }
10
+
11
+ /**
12
+ * Represents a value that tracks changes over time.
13
+ */
14
+ export interface Signal<out T> extends SignalLike<T> {
15
+ /** @ignore */
16
+ _effects: Set<Effect>;
17
+
18
+ /**
19
+ * Accesses the current value of the signal without tracking.
20
+ */
21
+ peek(): T;
22
+ }
23
+
24
+ export interface SetSignalOptions {
25
+ /**
26
+ * Whether to force the update of the signal even if the new value has the
27
+ * same reference.
28
+ */
29
+ force?: boolean;
30
+ /**
31
+ * Whether to suppress the update of the signal's effects.
32
+ */
33
+ silent?: boolean;
34
+ }
35
+
36
+ /**
37
+ * Can be used to update a signal with a new value.
38
+ */
39
+ export interface SignalSetter<in T, out U = T> {
40
+ (update: (value: U) => T, opts?: SetSignalOptions): void;
41
+ (value: T extends Function ? never : T, opts?: SetSignalOptions): void;
42
+ }
43
+
44
+ export interface SubscopeOptions {
45
+ details?: object;
46
+ }
47
+
48
+ interface Effect {
49
+ _clean?: Cleanup;
50
+ _deps: Set<Signal<unknown>>;
51
+ _scope: Scope;
52
+
53
+ _run(): void;
54
+ }
55
+
56
+ /**
57
+ * Represents the cleanup function of an effect.
58
+ */
59
+ export type Cleanup = (() => void) | void | undefined | null;
60
+
61
+ export interface Scope<out T = {}> {
62
+ readonly _parent?: Scope;
63
+ _effects: Effect[];
64
+ _subscopes: Scope[];
65
+ _details: T;
66
+
67
+ _run<T>(fn: () => T): T;
68
+ _cleanup(): void;
69
+ }
70
+
71
+ const createScope = (parent?: Scope): Scope => {
72
+ return {
73
+ _parent: parent,
74
+ _effects: [],
75
+ _subscopes: [],
76
+ _details: { ...parent?._details },
77
+
78
+ _run<T>(fn: () => T): T {
79
+ const prevScope = currScope;
80
+ currScope = this;
81
+
82
+ try {
83
+ return fn();
84
+ } finally {
85
+ currScope = prevScope;
86
+ }
87
+ },
88
+
89
+ _cleanup(): void {
90
+ for (let i = this._subscopes.length - 1; i >= 0; i--) {
91
+ this._subscopes[i]._cleanup();
92
+ }
93
+
94
+ this._subscopes = [];
95
+
96
+ for (let i = this._effects.length - 1; i >= 0; i--) {
97
+ const effect = this._effects[i];
98
+ effect._clean?.();
99
+
100
+ effect._deps.forEach((signal) => signal._effects.delete(effect));
101
+ effect._deps.clear();
102
+ }
103
+
104
+ this._effects = [];
105
+ },
106
+ };
107
+ };
108
+
109
+ let rootScope: Scope = createScope();
110
+ let currScope: Scope = rootScope;
111
+ let currUntracked: boolean = false;
112
+ let currEffect: Effect | undefined;
113
+ let currBatch:
114
+ | {
115
+ _setters: (() => void)[];
116
+ _effects: Set<Effect>;
117
+ }
118
+ | undefined;
119
+
120
+ /** @ignore */
121
+ export const useScope = <T = {}>(): Scope<T> => currScope as Scope<T>;
122
+
123
+ /**
124
+ * Creates a new signal with the given value.
125
+ * @returns A tuple with the signal and its setter.
126
+ */
127
+ export const useSignal: (<T>(
128
+ value: T,
129
+ opts?: SetSignalOptions,
130
+ ) => readonly [Signal<T>, SignalSetter<T>]) &
131
+ (<T>(
132
+ value?: T,
133
+ opts?: SetSignalOptions,
134
+ ) => readonly [Signal<T | undefined>, SignalSetter<T | undefined>]) = <T>(
135
+ value: T,
136
+ opts?: SetSignalOptions,
137
+ ): readonly [Signal<T>, SignalSetter<T>] => {
138
+ const signal: Signal<T> = () => {
139
+ if (!currUntracked && currEffect) {
140
+ currEffect._deps.add(signal);
141
+ signal._effects.add(currEffect);
142
+ }
143
+
144
+ return signal.peek();
145
+ };
146
+
147
+ signal._effects = new Set();
148
+ signal.peek = () => value;
149
+
150
+ const setter = (arg: T | ((value: T) => T), innerOpts?: SetSignalOptions) => {
151
+ innerOpts = { ...opts, ...innerOpts };
152
+
153
+ if (currBatch) {
154
+ const newValue =
155
+ typeof arg == "function"
156
+ ? (arg as (value: T) => T)(signal.peek())
157
+ : arg;
158
+
159
+ if (innerOpts?.force || newValue !== signal.peek()) {
160
+ if (innerOpts?.force) {
161
+ value = newValue;
162
+ } else {
163
+ currBatch._setters.push(() => (value = newValue));
164
+ }
165
+
166
+ if (!innerOpts?.silent) {
167
+ signal._effects.forEach((effect) => currBatch!._effects.add(effect));
168
+ }
169
+ }
170
+ } else {
171
+ useBatch(() => setter(arg, innerOpts));
172
+ }
173
+ };
174
+
175
+ return [signal, setter as any];
176
+ };
177
+
178
+ /**
179
+ * Runs the given function in a batch.
180
+ *
181
+ * @param fn Any calls to signal setters inside the function will be batched
182
+ * and updated at the same time.
183
+ */
184
+ export const useBatch = <T>(fn: () => T): T => {
185
+ const prevBatch = currBatch;
186
+ currBatch = { _setters: [], _effects: new Set() };
187
+
188
+ try {
189
+ const result = fn();
190
+
191
+ while (currBatch._setters.length > 0 || currBatch._effects.size > 0) {
192
+ // Clean effect subscope
193
+
194
+ const effects = currBatch._effects;
195
+ currBatch._effects = new Set();
196
+
197
+ effects.forEach((effect) => effect._clean?.());
198
+
199
+ // Run signal updates
200
+
201
+ currBatch._setters.forEach((setter) => setter());
202
+ currBatch._setters = [];
203
+
204
+ // Run effects
205
+
206
+ effects.forEach((effect) => effect._run());
207
+ }
208
+
209
+ return result;
210
+ } finally {
211
+ currBatch = prevBatch;
212
+ }
213
+ };
214
+
215
+ /**
216
+ * Creates an effect which will rerun when any accessed signal changes.
217
+ *
218
+ * @param fn The function to run; it can return a cleanup function.
219
+ */
220
+ export const useEffect = (
221
+ fn: () => Cleanup,
222
+ deps?: SignalLike<unknown>[],
223
+ ): void => {
224
+ const untracked = !!deps;
225
+
226
+ const effect: Effect = {
227
+ _scope: currScope,
228
+ _deps: new Set(),
229
+
230
+ _run(): void {
231
+ const prevEffect = currEffect;
232
+ const prevUntracked = currUntracked;
233
+
234
+ currEffect = this;
235
+
236
+ try {
237
+ if (!deps) {
238
+ // For automatic dependency tracking
239
+ // clean up dependencies and listeners
240
+
241
+ this._deps.forEach((dep) => dep._effects.delete(this));
242
+ this._deps.clear();
243
+ } else if (!this._deps.size) {
244
+ // Track specified dependencies
245
+
246
+ deps.forEach((dep) => dep());
247
+ }
248
+
249
+ // Run effect
250
+
251
+ currUntracked = untracked;
252
+
253
+ this._clean?.();
254
+
255
+ const cleanup = this._scope._run(() => useBatch(fn));
256
+
257
+ this._clean = !cleanup
258
+ ? null
259
+ : () => {
260
+ this._scope._run(() => useBatch(cleanup));
261
+ this._clean = null;
262
+ };
263
+ } finally {
264
+ // Restore scope state
265
+
266
+ currEffect = prevEffect;
267
+ currUntracked = prevUntracked;
268
+ }
269
+ },
270
+ };
271
+
272
+ effect._deps.forEach((signal) => signal._effects.add(effect));
273
+ currScope._effects.push(effect);
274
+ effect._run();
275
+
276
+ if (!effect._deps.size && !effect._clean) {
277
+ // Optimization: Destroy effect since there's no cleanup and this effect
278
+ // won't be called again
279
+
280
+ currScope._effects.pop();
281
+ }
282
+ };
283
+
284
+ /**
285
+ * Creates a memoized signal.
286
+ *
287
+ * @param fn The computation function.
288
+ */
289
+ export const useMemo = <T>(fn: () => T, opts?: SetSignalOptions): Signal<T> => {
290
+ const [memo, setMemo] = useSignal<T>();
291
+
292
+ let firstTime = true;
293
+
294
+ useEffect(() => {
295
+ setMemo(fn, firstTime ? { ...opts, force: true } : opts);
296
+
297
+ firstTime = false;
298
+ });
299
+
300
+ return memo as Signal<T>;
301
+ };
302
+
303
+ /**
304
+ * Executes a function inside a subscope which can be manually destroyed.
305
+ *
306
+ * @param fn The function to run in the subscope.
307
+ * @returns A function to manually destroy the subscope.
308
+ */
309
+ export const useSubscope = <T>(
310
+ fn: () => T,
311
+ opts?: SubscopeOptions,
312
+ ): [T, () => void] => {
313
+ const parent = currScope;
314
+ const scope = createScope(parent);
315
+ Object.assign(scope._details, opts?.details);
316
+
317
+ parent._subscopes.push(scope);
318
+ const result = scope._run(fn);
319
+
320
+ return [
321
+ result,
322
+ () => {
323
+ const index = parent._subscopes.indexOf(scope);
324
+ if (index >= 0) {
325
+ parent._subscopes.splice(index, 1);
326
+ }
327
+
328
+ scope._cleanup();
329
+ },
330
+ ];
331
+ };
332
+
333
+ /**
334
+ * Provide write capabilities to a signal.
335
+ */
336
+ export interface RefSignal<in out T> extends Signal<T>, RefSignalSetter<T> {
337
+ /**
338
+ * Sets the value of the signal.
339
+ */
340
+ set: SignalSetter<T>;
341
+ }
342
+
343
+ /**
344
+ * A contravariant variant of {@link RefSignal}.
345
+ */
346
+ export interface RefSignalSetter<in T> {
347
+ /**
348
+ * Sets the value of the signal.
349
+ */
350
+ set: SignalSetter<T, unknown>;
351
+ }
352
+
353
+ /**
354
+ * Creates a new signal with write capabilities.
355
+ */
356
+ export const useRef: (<T>(value: T, opts?: SetSignalOptions) => RefSignal<T>) &
357
+ (<T>(value?: T, opts?: SetSignalOptions) => RefSignal<T | undefined>) = <T>(
358
+ value?: T,
359
+ opts?: SetSignalOptions,
360
+ ): RefSignal<T> & RefSignal<T | undefined> => {
361
+ const [signal, setter] = useSignal(value, opts);
362
+ (signal as RefSignal<T | undefined>).set = setter;
363
+ return signal as RefSignal<T> & RefSignal<T | undefined>;
364
+ };
365
+
366
+ /**
367
+ * Represents a value that can be a signal or a constant value.
368
+ *
369
+ * Note that functions are not allowed as constant values.
370
+ */
371
+ export type MaybeSignal<T> = SignalLike<T> | (T extends Function ? never : T);
372
+
373
+ /**
374
+ * @namespace
375
+ */
376
+ export const MaybeSignal = {
377
+ /**
378
+ * Transforms the given {@link MaybeSignal} into a {@link Signal}.
379
+ */
380
+ upgrade:
381
+ <T>(signal: MaybeSignal<T>): SignalLike<T> =>
382
+ () =>
383
+ MaybeSignal.get(signal),
384
+
385
+ /**
386
+ * Gets the value of the given {@link MaybeSignal}.
387
+ */
388
+ get: <T>(signal: MaybeSignal<T>): T =>
389
+ typeof signal == "function" ? (signal as SignalLike<T>)() : signal,
390
+
391
+ /**
392
+ * Accesses the value of the given {@link MaybeSignal} without tracking.
393
+ */
394
+ peek<T>(signal: MaybeSignal<T>): T {
395
+ const prevUntracked = currUntracked;
396
+ currUntracked = true;
397
+
398
+ try {
399
+ return this.get(signal);
400
+ } finally {
401
+ currUntracked = prevUntracked;
402
+ }
403
+ },
404
+ };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Represents a render result of a component.
3
+ */
4
+ export interface Template {
5
+ /**
6
+ * Build the DOM elements represented by this template.
7
+ */
8
+ build(): Node[];
9
+ }
10
+
11
+ export const createTemplate = (build: () => Template | Node[]): Template => ({
12
+ build() {
13
+ const nodes = build();
14
+ return (nodes as Template).build?.() ?? nodes;
15
+ },
16
+ });
package/src/utils.ts ADDED
@@ -0,0 +1,29 @@
1
+ type RemoveOn<S extends string> = S extends `on${infer R}`
2
+ ? Uncapitalize<R>
3
+ : never;
4
+
5
+ export type CamelCaseToKebabCase<S extends string> =
6
+ S extends `${infer F}${infer R}`
7
+ ? F extends Lowercase<F>
8
+ ? `${F}${CamelCaseToKebabCase<R>}`
9
+ : `-${Lowercase<F>}${CamelCaseToKebabCase<R>}`
10
+ : Lowercase<S>;
11
+
12
+ export const camelCaseToKebabCase = (value: string): string => {
13
+ return (
14
+ (value[0] ?? "").toLowerCase() +
15
+ value.slice(1).replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)
16
+ );
17
+ };
18
+
19
+ export type JsxPropNameToEventName<S extends string> = CamelCaseToKebabCase<
20
+ RemoveOn<S>
21
+ >;
22
+
23
+ export const jsxPropNameToEventName = (value: `on${string}`): string => {
24
+ if (value.startsWith("on:")) {
25
+ return value.slice(3);
26
+ } else {
27
+ return camelCaseToKebabCase(value.slice(2));
28
+ }
29
+ };
@@ -0,0 +1,16 @@
1
+ {
2
+ "compress": {
3
+ "ecma": "2023",
4
+ "pure_new": true,
5
+ "toplevel": true,
6
+ "unsafe": true,
7
+ "unsafe_symbols": true
8
+ },
9
+ "mangle": {
10
+ "toplevel": true,
11
+ "module": true,
12
+ "properties": {
13
+ "regex": "^_(?!_)"
14
+ }
15
+ }
16
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "rootDir": "./src",
4
+ "outDir": "./dist",
5
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
6
+ "module": "NodeNext",
7
+ "moduleResolution": "NodeNext",
8
+ "target": "ESNext",
9
+ "jsx": "react-jsx",
10
+ "jsxImportSource": "sinho",
11
+ "declaration": true,
12
+ "sourceMap": true,
13
+ "strict": true,
14
+ "skipLibCheck": true
15
+ },
16
+ "include": ["./src/**/*"],
17
+ "exclude": []
18
+ }
package/web/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # Website
2
+
3
+ This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
4
+
5
+ ### Installation
6
+
7
+ ```
8
+ $ yarn
9
+ ```
10
+
11
+ ### Local Development
12
+
13
+ ```
14
+ $ yarn start
15
+ ```
16
+
17
+ This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
18
+
19
+ ### Build
20
+
21
+ ```
22
+ $ yarn build
23
+ ```
24
+
25
+ This command generates static content into the `build` directory and can be served using any static contents hosting service.
26
+
27
+ ### Deployment
28
+
29
+ Using SSH:
30
+
31
+ ```
32
+ $ USE_SSH=true yarn deploy
33
+ ```
34
+
35
+ Not using SSH:
36
+
37
+ ```
38
+ $ GIT_USER=<Your GitHub username> yarn deploy
39
+ ```
40
+
41
+ If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
3
+ };