oxiui-lib 0.2.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 (64) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +15 -0
  3. package/biome.json +34 -0
  4. package/bun.lock +71 -0
  5. package/dist/core/elements/AbaComponent.d.ts +36 -0
  6. package/dist/core/elements/AbaComponent.js +114 -0
  7. package/dist/core/elements/AbaStateFull.d.ts +20 -0
  8. package/dist/core/elements/AbaStateFull.js +7 -0
  9. package/dist/core/elements/Animation.d.ts +31 -0
  10. package/dist/core/elements/Animation.js +83 -0
  11. package/dist/core/elements/Button.d.ts +23 -0
  12. package/dist/core/elements/Button.js +15 -0
  13. package/dist/core/elements/HRow.d.ts +4 -0
  14. package/dist/core/elements/HRow.js +11 -0
  15. package/dist/core/elements/InputText.d.ts +25 -0
  16. package/dist/core/elements/InputText.js +28 -0
  17. package/dist/core/elements/Stack.d.ts +26 -0
  18. package/dist/core/elements/Stack.js +40 -0
  19. package/dist/core/elements/Text.d.ts +40 -0
  20. package/dist/core/elements/Text.js +45 -0
  21. package/dist/core/elements/VRow.d.ts +4 -0
  22. package/dist/core/elements/VRow.js +11 -0
  23. package/dist/core/elements/index.d.ts +10 -0
  24. package/dist/core/elements/index.js +26 -0
  25. package/dist/core/elements/ui.d.ts +1 -0
  26. package/dist/core/elements/ui.js +6 -0
  27. package/dist/core/renderer.d.ts +3 -0
  28. package/dist/core/renderer.js +15 -0
  29. package/dist/core/renderers/html.d.ts +22 -0
  30. package/dist/core/renderers/html.js +201 -0
  31. package/dist/core/scheduler.d.ts +10 -0
  32. package/dist/core/scheduler.js +102 -0
  33. package/dist/core/signals.d.ts +13 -0
  34. package/dist/core/signals.js +77 -0
  35. package/dist/core/tree.d.ts +5 -0
  36. package/dist/core/tree.js +18 -0
  37. package/dist/core/types/component.d.ts +21 -0
  38. package/dist/core/types/component.js +2 -0
  39. package/dist/core/types/html.d.ts +66 -0
  40. package/dist/core/types/html.js +27 -0
  41. package/dist/index.d.ts +3 -0
  42. package/dist/index.js +19 -0
  43. package/lefthook.yml +6 -0
  44. package/package.json +23 -0
  45. package/src/core/elements/Animation.ts +80 -0
  46. package/src/core/elements/Button.ts +14 -0
  47. package/src/core/elements/HRow.ts +8 -0
  48. package/src/core/elements/InputText.ts +25 -0
  49. package/src/core/elements/OxiComponent.ts +143 -0
  50. package/src/core/elements/OxiStateFull.ts +3 -0
  51. package/src/core/elements/Stack.ts +44 -0
  52. package/src/core/elements/Text.ts +53 -0
  53. package/src/core/elements/VRow.ts +8 -0
  54. package/src/core/elements/index.ts +10 -0
  55. package/src/core/elements/ui.ts +5 -0
  56. package/src/core/renderer.ts +12 -0
  57. package/src/core/renderers/html.ts +268 -0
  58. package/src/core/scheduler.ts +124 -0
  59. package/src/core/signals.ts +84 -0
  60. package/src/core/tree.ts +20 -0
  61. package/src/core/types/component.ts +25 -0
  62. package/src/core/types/html.ts +97 -0
  63. package/src/index.ts +3 -0
  64. package/tsconfig.json +12 -0
@@ -0,0 +1,10 @@
1
+ export * from "./Animation.js";
2
+ export * from "./Button.js";
3
+ export * from "./HRow.js";
4
+ export * from "./InputText.js";
5
+ export * from "./OxiComponent.js";
6
+ export * from "./OxiStateFull.js";
7
+ export * from "./Stack.js";
8
+ export * from "./Text.js";
9
+ export * from "./ui.js";
10
+ export * from "./VRow.js";
@@ -0,0 +1,5 @@
1
+ export function createStyle<
2
+ T extends Record<string, Partial<CSSStyleDeclaration>>,
3
+ >(style: T): T {
4
+ return style;
5
+ }
@@ -0,0 +1,12 @@
1
+ let globalCursor = 0;
2
+
3
+ export function getGlobalCursor() {
4
+ return globalCursor;
5
+ }
6
+ export function nextCursor() {
7
+ return globalCursor++;
8
+ }
9
+
10
+ export function resetCursor() {
11
+ globalCursor = 0;
12
+ }
@@ -0,0 +1,268 @@
1
+ import type { IComponent } from "../types/component.js";
2
+ import {
3
+ type CommonProps,
4
+ type InputProps,
5
+ type IntrinsicElements,
6
+ } from "../types/html.js";
7
+
8
+ export class HTMLRenderer {
9
+ private rootBody: HTMLElement;
10
+ private oldRoot?: IComponent<keyof IntrinsicElements>;
11
+ private nodeMap: Map<string, HTMLElement> = new Map();
12
+
13
+ constructor(rootId: string = "app") {
14
+ const el = document.getElementById(rootId);
15
+ if (!el) throw new Error(`Root element #${rootId} not found`);
16
+ this.rootBody = el;
17
+ }
18
+
19
+ render(
20
+ stateId: string,
21
+ readers: Set<string>,
22
+ tree: IComponent<keyof IntrinsicElements>,
23
+ ) {
24
+ if (!this.oldRoot) {
25
+ this.oldRoot = this.cloneTree(tree);
26
+ this.replaceRoot(this.oldRoot);
27
+ return;
28
+ }
29
+
30
+ const affected = this.findAllByState(this.oldRoot, stateId);
31
+ const topMost = this.getTopMost(Array.from(affected).map((n) => n.index));
32
+
33
+ for (const index of topMost) {
34
+ const newNode = this.findByIndex(tree, index);
35
+ const oldNode = this.findByIndex(this.oldRoot, index);
36
+
37
+ if (newNode && oldNode && this.structureChanged(oldNode, newNode)) {
38
+ this.replaceNode(index, newNode);
39
+ this.oldRoot = this.cloneTree(tree);
40
+ this.nodeMap.forEach((e, k) => {
41
+ // clean map
42
+ if (!this.findByIndex(this.oldRoot!, k)) {
43
+ this.nodeMap.delete(k);
44
+ }
45
+ });
46
+ return;
47
+ }
48
+ }
49
+
50
+ for (const id of readers) {
51
+ const compNode = this.findByIndex(tree, id);
52
+ if (!compNode) continue;
53
+
54
+ const domNode = this.nodeMap.get(id);
55
+ if (!domNode) continue;
56
+
57
+ this.applyProps(domNode, compNode);
58
+ }
59
+
60
+ console.log(this.nodeMap);
61
+ }
62
+
63
+ private findByIndex(
64
+ node: IComponent<keyof IntrinsicElements>,
65
+ index: string,
66
+ ): IComponent<keyof IntrinsicElements> | undefined {
67
+ if (node.index === index) return node;
68
+
69
+ for (const child of node.down) {
70
+ const found = this.findByIndex(child, index);
71
+ if (found) return found;
72
+ }
73
+
74
+ return undefined;
75
+ }
76
+
77
+ private findAllByState(
78
+ node: IComponent<keyof IntrinsicElements>,
79
+ index: string,
80
+ acc: Set<IComponent<keyof IntrinsicElements>> = new Set(),
81
+ ): Set<IComponent<keyof IntrinsicElements>> {
82
+ if (node.cursors?.has(index)) {
83
+ acc.add(node);
84
+ }
85
+
86
+ for (const child of node.down) {
87
+ this.findAllByState(child, index, acc);
88
+ }
89
+
90
+ return acc;
91
+ }
92
+
93
+ private structureChanged(
94
+ prev: IComponent<keyof IntrinsicElements> | undefined,
95
+ next: IComponent<keyof IntrinsicElements>,
96
+ ): boolean {
97
+ if (!prev) return true;
98
+ if (prev.type !== next.type) return true;
99
+ if (prev.down.length !== next.down.length) return true;
100
+
101
+ for (let i = 0; i < next.down.length; i++) {
102
+ if (next.down[i] !== undefined) {
103
+ if (
104
+ this.structureChanged(
105
+ prev.down[i],
106
+ next.down[i] as IComponent<keyof IntrinsicElements>,
107
+ )
108
+ ) {
109
+ return true;
110
+ }
111
+ }
112
+ }
113
+
114
+ return false;
115
+ }
116
+
117
+ private cloneTree(
118
+ node: IComponent<keyof IntrinsicElements>,
119
+ ): IComponent<keyof IntrinsicElements> {
120
+ return {
121
+ type: node.type,
122
+ index: node.index,
123
+ cursors: new Set(node.cursors),
124
+ down: node.down.map((n) => this.cloneTree(n)),
125
+ props: node.props,
126
+ } as IComponent<keyof IntrinsicElements>;
127
+ }
128
+
129
+ private renderNode(node: IComponent<keyof IntrinsicElements>): HTMLElement {
130
+ if (node.type === "semanticText") return this.renderTextNode(node);
131
+
132
+ const element = document.createElement(node.type);
133
+ element.setAttribute("id", node.index);
134
+
135
+ this.nodeMap.set(node.index, element);
136
+
137
+ this.applyProps(element, node);
138
+
139
+ for (const child of node.down) {
140
+ element.appendChild(this.renderNode(child));
141
+ }
142
+
143
+ return element;
144
+ }
145
+
146
+ private renderTextNode(
147
+ node: IComponent<keyof IntrinsicElements>,
148
+ ): HTMLElement {
149
+ const props = node.props as IntrinsicElements["semanticText"];
150
+
151
+ const element = document.createElement(props.type);
152
+ element.setAttribute("id", node.index);
153
+ this.nodeMap.set(node.index, element);
154
+
155
+ this.applyProps(element, node);
156
+
157
+ for (const child of node.down) {
158
+ element.appendChild(this.renderNode(child));
159
+ }
160
+
161
+ return element;
162
+ }
163
+
164
+ private replaceRoot(component: IComponent<keyof IntrinsicElements>) {
165
+ const fragment = document.createDocumentFragment();
166
+
167
+ for (const child of component.down) {
168
+ fragment.appendChild(this.renderNode(child));
169
+ }
170
+
171
+ this.rootBody.replaceChildren(fragment);
172
+ }
173
+
174
+ private replaceNode(
175
+ index: string,
176
+ node: IComponent<keyof IntrinsicElements>,
177
+ ) {
178
+ if (index === "0") this.replaceRoot(node);
179
+ const oldNode = this.findByIndex(this.oldRoot!, index);
180
+ if (!oldNode) return;
181
+
182
+ const domNode = this.nodeMap.get(oldNode.index);
183
+ if (!domNode) return;
184
+
185
+ const newDom = this.renderNode(node);
186
+ domNode.replaceWith(newDom);
187
+ }
188
+
189
+ private getTopMost(indices: string[]): string[] {
190
+ const sorted = indices.sort((a, b) => a.length - b.length);
191
+ const result: string[] = [];
192
+
193
+ for (const value of sorted) {
194
+ const isChild = result.some((parent) => value.startsWith(parent + "."));
195
+ if (!isChild) result.push(value);
196
+ }
197
+
198
+ return result;
199
+ }
200
+
201
+ private applyProps(
202
+ element: HTMLElement,
203
+ node: IComponent<keyof IntrinsicElements>,
204
+ ) {
205
+ if (!node.props) return;
206
+
207
+ const props: CommonProps =
208
+ node.type === "semanticText"
209
+ ? (node.props as IntrinsicElements["semanticText"]).props
210
+ : (node.props as CommonProps);
211
+
212
+ this.applyCommonProps(element, props);
213
+
214
+ if (node.type === "input") {
215
+ this.applyInputProps(element as HTMLInputElement, props as InputProps);
216
+ }
217
+ }
218
+
219
+ private applyCommonProps(element: HTMLElement, props: CommonProps) {
220
+ if (props.content !== undefined) {
221
+ const value =
222
+ typeof props.content === "function" ? props.content() : props.content;
223
+
224
+ element.textContent = String(value);
225
+ }
226
+
227
+ this.applyStyle(element, props.style);
228
+
229
+ if (props.id) element.id = props.id;
230
+ if (props.class) element.className = props.class;
231
+ if (props.title) element.title = props.title;
232
+ if (props.hidden !== undefined) element.hidden = props.hidden;
233
+
234
+ element.onclick = props.onClick ?? null;
235
+ element.onchange = props.onChange ?? null;
236
+ element.onfocus = props.onFocus ?? null;
237
+ element.onblur = props.onBlur ?? null;
238
+ element.onkeydown = props.onKeyDown ?? null;
239
+ element.onkeyup = props.onKeyUp ?? null;
240
+
241
+ element.oninput = (ev: Event) => props.onInput?.(ev as InputEvent) ?? null;
242
+ }
243
+
244
+ private applyStyle(
245
+ element: HTMLElement,
246
+ style?: string | Partial<CSSStyleDeclaration>,
247
+ ) {
248
+ if (!style) return;
249
+
250
+ if (typeof style === "string") {
251
+ element.setAttribute("style", style);
252
+ } else {
253
+ Object.assign(element.style, style);
254
+ }
255
+ }
256
+
257
+ private applyInputProps(element: HTMLInputElement, props: InputProps) {
258
+ if (props.value !== undefined) {
259
+ element.value = String(props.value);
260
+ }
261
+
262
+ element.oninput = (ev: Event) => {
263
+ const input = ev.target as HTMLInputElement;
264
+ props.onTextChange?.(input.value);
265
+ props.onInput?.(ev as InputEvent);
266
+ };
267
+ }
268
+ }
@@ -0,0 +1,124 @@
1
+ import type { DivContainer } from "./elements/AbaStateFull";
2
+ import { resetCursor } from "./renderer";
3
+ import type { IComponent } from "./types/component";
4
+ import type { IntrinsicElements } from "./types/html";
5
+
6
+ type RenderCallback = (
7
+ stateId: string,
8
+ readers: Set<string>,
9
+ root: IComponent<keyof IntrinsicElements>,
10
+ ) => void;
11
+
12
+ let running = false;
13
+ let app: DivContainer | null = null;
14
+ let renderCallback: RenderCallback | null = null;
15
+ const postRenderQueue: (() => void)[] = [];
16
+ const resetTree = () => {
17
+ if (app?.down) {
18
+ app.down = [];
19
+ }
20
+ };
21
+
22
+ export function initScheduler(
23
+ App: new () => DivContainer,
24
+ renderer: RenderCallback,
25
+ ) {
26
+ app = new App();
27
+ renderCallback = renderer;
28
+ performRender("", new Set());
29
+ }
30
+
31
+ export function scheduleEffect(fn: () => void) {
32
+ postRenderQueue.push(fn);
33
+ }
34
+
35
+ function findByIndex(
36
+ node: IComponent<keyof IntrinsicElements>,
37
+ index: string,
38
+ ): IComponent<keyof IntrinsicElements> | null {
39
+ if (node.index === index) return node;
40
+
41
+ for (const child of node.down) {
42
+ const found = findByIndex(child, index);
43
+ if (found) return found;
44
+ }
45
+
46
+ return null;
47
+ }
48
+
49
+ function clearCursors(node?: IComponent<keyof IntrinsicElements>) {
50
+ if (!node) return;
51
+
52
+ node.cursors?.clear();
53
+
54
+ for (const child of node.down) {
55
+ clearCursors(child);
56
+ }
57
+ }
58
+
59
+ function performRender(stateId: string, readers: Set<string>) {
60
+ if (!app || !renderCallback) return;
61
+ console.clear();
62
+
63
+ resetTree();
64
+ resetCursor();
65
+ app.body();
66
+ renderCallback(stateId, readers, app as IComponent<keyof IntrinsicElements>);
67
+ postRenderQueue.forEach((fn) => fn());
68
+ postRenderQueue.length = 0;
69
+ clearCursors(app as IComponent<keyof IntrinsicElements>);
70
+ }
71
+
72
+ export function rerender(stateId: string, readers: Set<string>) {
73
+ if (!renderCallback || !app) return;
74
+ if (running) return;
75
+
76
+ const validComponents: Set<string> = new Set();
77
+
78
+ readers.forEach((id) => {
79
+ if (findByIndex(app as IComponent<keyof IntrinsicElements>, id)) {
80
+ validComponents.add(id);
81
+ }
82
+ });
83
+
84
+ running = true;
85
+
86
+ setTimeout(() => {
87
+ running = false;
88
+ performRender(stateId, validComponents);
89
+ }, 0);
90
+ }
91
+
92
+ let immediateRunning = false;
93
+ let frameId: number | null = null;
94
+
95
+ export function startImmediateMode() {
96
+ if (!app || !renderCallback) return;
97
+ if (immediateRunning) return;
98
+
99
+ immediateRunning = true;
100
+
101
+ const loop = () => {
102
+ if (!immediateRunning || !app) return;
103
+
104
+ resetTree();
105
+ resetCursor();
106
+ app.body();
107
+ renderCallback?.("", new Set(), app as IComponent<keyof IntrinsicElements>);
108
+ postRenderQueue.forEach((fn) => fn());
109
+ postRenderQueue.length = 0;
110
+ clearCursors(app as IComponent<keyof IntrinsicElements>);
111
+ frameId = requestAnimationFrame(loop);
112
+ };
113
+
114
+ frameId = requestAnimationFrame(loop);
115
+ }
116
+
117
+ export function stopImmediateMode() {
118
+ immediateRunning = false;
119
+
120
+ if (frameId !== null) {
121
+ cancelAnimationFrame(frameId);
122
+ frameId = null;
123
+ }
124
+ }
@@ -0,0 +1,84 @@
1
+ import { nextCursor } from "./renderer.js";
2
+ import { rerender } from "./scheduler.js";
3
+ import { current } from "./tree.js";
4
+
5
+ type SignalState<T> = {
6
+ value: T;
7
+ };
8
+
9
+ export class Signal<T> {
10
+ public constructor(public initial: T) {
11
+ this.cursor = nextCursor();
12
+ if (!Signal.#memory.has(this.cursor)) {
13
+ Signal.#memory.set(this.cursor, { value: initial });
14
+ }
15
+ this.state = Signal.#memory.get(this.cursor) as SignalState<T>;
16
+ }
17
+ private state: SignalState<T>;
18
+ private cursor: number;
19
+
20
+ public get value(): T {
21
+ const reader = Signal.getCurrentReaderWithCursorId(this.cursor);
22
+ if (!Signal.#readers.has(reader)) {
23
+ Signal.#readers.add(reader);
24
+ }
25
+ return this.state.value;
26
+ }
27
+
28
+ public set value(v: T) {
29
+ this.state.value = v;
30
+ if (this.cursor) {
31
+ rerender(`${this.cursor}`, Signal.getReadersWithCursor(this.cursor));
32
+ console.log(this.cursor, current());
33
+ }
34
+
35
+ Signal.clearReaders();
36
+ }
37
+
38
+ static #memory = new Map<number, { value: unknown }>();
39
+ static #readers = new Set<string>();
40
+
41
+ private static clearReaders() {
42
+ Signal.#readers.clear();
43
+ }
44
+
45
+ private static getCurrentReaderWithCursorId(cursor: number) {
46
+ const callee = current();
47
+ callee?.cursors?.add(cursor.toString());
48
+ const c = (callee?.down?.length ?? 0) - 1;
49
+ return `${callee?.index}.${(c < 0 ? 0 : c).toString()}-${cursor}`;
50
+ }
51
+
52
+ private static getReadersWithCursor(cursor: number) {
53
+ return new Set(
54
+ Array.from(Signal.#readers)
55
+ .filter((r) => r.endsWith(`-${cursor}`))
56
+ .map((v) => v.split("-")[0])
57
+ .filter((v): v is string => v !== undefined),
58
+ );
59
+ }
60
+
61
+ public static create<T>(initial: T): [() => T, (v: T) => void] {
62
+ const cursor = nextCursor();
63
+ if (!Signal.#memory.has(cursor)) {
64
+ Signal.#memory.set(cursor, { value: initial });
65
+ }
66
+ const state = Signal.#memory.get(cursor) as SignalState<T>;
67
+
68
+ const read = () => {
69
+ const reader = Signal.getCurrentReaderWithCursorId(cursor);
70
+ if (!Signal.#readers.has(reader)) {
71
+ Signal.#readers.add(reader);
72
+ }
73
+ return state.value;
74
+ };
75
+
76
+ const write = (v: T) => {
77
+ state.value = v;
78
+ rerender(`${cursor}`, Signal.getReadersWithCursor(cursor));
79
+ Signal.clearReaders();
80
+ };
81
+
82
+ return [read, write];
83
+ }
84
+ }
@@ -0,0 +1,20 @@
1
+ import type { IComponent } from "./types/component";
2
+ import type { IntrinsicElements } from "./types/html";
3
+
4
+ const stack: IComponent<keyof IntrinsicElements>[] = [];
5
+
6
+ export function current() {
7
+ return stack[stack.length - 1];
8
+ }
9
+
10
+ function _open(component: IComponent<keyof IntrinsicElements>) {
11
+ stack.push(component);
12
+ }
13
+
14
+ export function open(component: IComponent<any>) {
15
+ stack.push(component as IComponent<keyof IntrinsicElements>);
16
+ }
17
+
18
+ export function close() {
19
+ stack.pop();
20
+ }
@@ -0,0 +1,25 @@
1
+ import type { IntrinsicElements } from "./html.js";
2
+
3
+ export type StyleProp<P> = P extends { style?: infer ST }
4
+ ? ST
5
+ : string | Partial<CSSStyleDeclaration>;
6
+
7
+ export interface IComponent<K extends keyof IntrinsicElements> {
8
+ index: string;
9
+ props: IntrinsicElements[K];
10
+ cursors: Set<string>;
11
+ type: K;
12
+ down: IComponent<keyof IntrinsicElements>[];
13
+ up?: IComponent<keyof IntrinsicElements>;
14
+ view?: () => void;
15
+ body(fn: (component: this) => void): this;
16
+ onAppear(fn: (component: this) => void): this;
17
+ withProps<T extends Partial<IntrinsicElements[K]>>(newProps: T): this;
18
+ withStyle(style: StyleProp<IntrinsicElements[K]>): this;
19
+ withExtension(extension: (component: this) => void): this;
20
+ }
21
+
22
+ export interface IScreen<K extends keyof IntrinsicElements>
23
+ extends IComponent<K> {
24
+ render(): void;
25
+ }
@@ -0,0 +1,97 @@
1
+ export type EventHandler<E = Event> = (event: E) => void;
2
+
3
+ export type CommonProps = {
4
+ id?: string;
5
+ class?: string;
6
+ style?: Partial<CSSStyleDeclaration>;
7
+ title?: string;
8
+ hidden?: boolean;
9
+
10
+ onClick?: EventHandler<MouseEvent>;
11
+ onInput?: EventHandler<InputEvent>;
12
+ onChange?: EventHandler<Event>;
13
+ onFocus?: EventHandler<FocusEvent>;
14
+ onBlur?: EventHandler<FocusEvent>;
15
+ onKeyDown?: EventHandler<KeyboardEvent>;
16
+ onKeyUp?: EventHandler<KeyboardEvent>;
17
+
18
+ content?: string | number | (() => string | number);
19
+ };
20
+
21
+ export type ButtonProps = CommonProps & {
22
+ type?: "button" | "submit" | "reset";
23
+ disabled?: boolean;
24
+ };
25
+
26
+ export type InputProps = CommonProps & {
27
+ type?:
28
+ | "text"
29
+ | "email"
30
+ | "password"
31
+ | "number"
32
+ | "date"
33
+ | "time"
34
+ | "datetime-local"
35
+ | "month"
36
+ | "week"
37
+ | "url"
38
+ | "search"
39
+ | "tel"
40
+ | "color";
41
+ value?: string | number;
42
+ placeholder?: string;
43
+ required?: boolean;
44
+ onTextChange?: EventHandler<string>;
45
+ };
46
+
47
+ export type SemanticText = {
48
+ h1: CommonProps;
49
+ h2: CommonProps;
50
+ h3: CommonProps;
51
+ h4: CommonProps;
52
+ h5: CommonProps;
53
+ body: CommonProps;
54
+ p: CommonProps;
55
+ span: CommonProps;
56
+ };
57
+
58
+ export type IntrinsicElements = {
59
+ root: CommonProps;
60
+ div: CommonProps;
61
+ span: CommonProps;
62
+ semanticText: {
63
+ type: keyof SemanticText;
64
+ props: CommonProps;
65
+ };
66
+ animation: CommonProps;
67
+ button: ButtonProps;
68
+ input: InputProps;
69
+ form: CommonProps;
70
+ label: CommonProps;
71
+ a: CommonProps & { href?: string; target?: string };
72
+ img: CommonProps & { src: string; alt?: string };
73
+ textarea: CommonProps & { value?: string };
74
+ text: { text: string };
75
+ };
76
+
77
+ function isObject(value: unknown): value is Record<string, unknown> {
78
+ return typeof value === "object" && value !== null && !Array.isArray(value);
79
+ }
80
+
81
+ export function isCommonProps(props: unknown): props is CommonProps {
82
+ if (!isObject(props)) return false;
83
+
84
+ if ("id" in props && typeof props.id !== "string") return false;
85
+ if ("class" in props && typeof props.class !== "string") return false;
86
+ if ("title" in props && typeof props.title !== "string") return false;
87
+ if ("hidden" in props && typeof props.hidden !== "boolean") return false;
88
+
89
+ if ("content" in props) {
90
+ const c = props.content;
91
+ const valid =
92
+ typeof c === "string" || typeof c === "number" || typeof c === "function";
93
+ if (!valid) return false;
94
+ }
95
+
96
+ return true;
97
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./core/elements";
2
+ export * from "./core/scheduler";
3
+ export * from "./core/signals";
package/tsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2019",
4
+ "module": "commonjs",
5
+ "declaration": true,
6
+ "outDir": "./dist",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true
10
+ },
11
+ "include": ["src"]
12
+ }