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.
- package/.github/workflows/ci.yml +24 -0
- package/.github/workflows/deploy-docs.yml +47 -0
- package/.prettierrc +3 -0
- package/LICENSE.md +21 -0
- package/README.md +33 -0
- package/ci/check-size.js +8 -0
- package/dist/array_mutation.d.ts +16 -0
- package/dist/array_mutation.js +75 -0
- package/dist/array_mutation.js.map +1 -0
- package/dist/bundle.d.ts +1126 -0
- package/dist/bundle.js +1074 -0
- package/dist/bundle.min.js +1 -0
- package/dist/component.d.ts +253 -0
- package/dist/component.js +256 -0
- package/dist/component.js.map +1 -0
- package/dist/context.d.ts +21 -0
- package/dist/context.js +34 -0
- package/dist/context.js.map +1 -0
- package/dist/create_element.d.ts +43 -0
- package/dist/create_element.js +43 -0
- package/dist/create_element.js.map +1 -0
- package/dist/dom.d.ts +602 -0
- package/dist/dom.js +97 -0
- package/dist/dom.js.map +1 -0
- package/dist/intrinsic/ClassComponent.d.ts +2 -0
- package/dist/intrinsic/ClassComponent.js +10 -0
- package/dist/intrinsic/ClassComponent.js.map +1 -0
- package/dist/intrinsic/Dynamic.d.ts +33 -0
- package/dist/intrinsic/Dynamic.js +53 -0
- package/dist/intrinsic/Dynamic.js.map +1 -0
- package/dist/intrinsic/ErrorBoundary.d.ts +14 -0
- package/dist/intrinsic/ErrorBoundary.js +36 -0
- package/dist/intrinsic/ErrorBoundary.js.map +1 -0
- package/dist/intrinsic/For.d.ts +10 -0
- package/dist/intrinsic/For.js +81 -0
- package/dist/intrinsic/For.js.map +1 -0
- package/dist/intrinsic/Fragment.d.ts +23 -0
- package/dist/intrinsic/Fragment.js +28 -0
- package/dist/intrinsic/Fragment.js.map +1 -0
- package/dist/intrinsic/If.d.ts +24 -0
- package/dist/intrinsic/If.js +47 -0
- package/dist/intrinsic/If.js.map +1 -0
- package/dist/intrinsic/Portal.d.ts +6 -0
- package/dist/intrinsic/Portal.js +15 -0
- package/dist/intrinsic/Portal.js.map +1 -0
- package/dist/intrinsic/Style.d.ts +7 -0
- package/dist/intrinsic/Style.js +70 -0
- package/dist/intrinsic/Style.js.map +1 -0
- package/dist/intrinsic/TagComponent.d.ts +4 -0
- package/dist/intrinsic/TagComponent.js +67 -0
- package/dist/intrinsic/TagComponent.js.map +1 -0
- package/dist/intrinsic/Text.d.ts +6 -0
- package/dist/intrinsic/Text.js +16 -0
- package/dist/intrinsic/Text.js.map +1 -0
- package/dist/intrinsic/mod.d.ts +5 -0
- package/dist/intrinsic/mod.js +6 -0
- package/dist/intrinsic/mod.js.map +1 -0
- package/dist/jsx-runtime/mod.d.ts +23 -0
- package/dist/jsx-runtime/mod.js +11 -0
- package/dist/jsx-runtime/mod.js.map +1 -0
- package/dist/mod.d.ts +8 -0
- package/dist/mod.js +7 -0
- package/dist/mod.js.map +1 -0
- package/dist/renderer.d.ts +13 -0
- package/dist/renderer.js +25 -0
- package/dist/renderer.js.map +1 -0
- package/dist/scope.d.ts +138 -0
- package/dist/scope.js +228 -0
- package/dist/scope.js.map +1 -0
- package/dist/template.d.ts +10 -0
- package/dist/template.js +7 -0
- package/dist/template.js.map +1 -0
- package/dist/utils.d.ts +6 -0
- package/dist/utils.js +13 -0
- package/dist/utils.js.map +1 -0
- package/package.json +71 -0
- package/src/array_mutation.ts +118 -0
- package/src/component.ts +624 -0
- package/src/context.ts +70 -0
- package/src/create_element.ts +89 -0
- package/src/dom.ts +819 -0
- package/src/intrinsic/ClassComponent.ts +17 -0
- package/src/intrinsic/For.ts +122 -0
- package/src/intrinsic/Fragment.ts +38 -0
- package/src/intrinsic/If.ts +73 -0
- package/src/intrinsic/Portal.ts +25 -0
- package/src/intrinsic/Style.ts +120 -0
- package/src/intrinsic/TagComponent.ts +102 -0
- package/src/intrinsic/Text.ts +24 -0
- package/src/intrinsic/mod.ts +5 -0
- package/src/jsx-runtime/mod.ts +41 -0
- package/src/mod.ts +37 -0
- package/src/renderer.ts +45 -0
- package/src/scope.ts +404 -0
- package/src/template.ts +16 -0
- package/src/utils.ts +29 -0
- package/terser.config.json +16 -0
- package/tsconfig.json +18 -0
- package/web/README.md +41 -0
- package/web/babel.config.js +3 -0
- package/web/dist/shingo.min.d.ts +1131 -0
- package/web/dist/shingo.min.js +1 -0
- package/web/docusaurus.config.ts +151 -0
- package/web/package-lock.json +14850 -0
- package/web/package.json +54 -0
- package/web/sidebars.ts +31 -0
- package/web/src/components/monacoEditor.tsx +72 -0
- package/web/src/components/playground.tsx +89 -0
- package/web/src/components/playgroundComponent.tsx +168 -0
- package/web/src/css/custom.css +37 -0
- package/web/src/pages/index.module.css +31 -0
- package/web/src/pages/index.tsx +73 -0
- package/web/src/pages/playground.tsx +64 -0
- package/web/static/.nojekyll +0 -0
- package/web/static/dist/bundle.d.ts +1126 -0
- package/web/static/dist/bundle.min.js +1 -0
- 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
|
+
};
|
package/src/template.ts
ADDED
|
@@ -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
|
+
};
|
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.
|