@hypen-space/web 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.
- package/dist/chunk-2s02mkzs.js +32 -0
- package/dist/chunk-2s02mkzs.js.map +9 -0
- package/dist/src/canvas/accessibility.js +152 -0
- package/dist/src/canvas/accessibility.js.map +10 -0
- package/dist/src/canvas/events.js +198 -0
- package/dist/src/canvas/events.js.map +10 -0
- package/dist/src/canvas/index.js +28 -0
- package/dist/src/canvas/index.js.map +9 -0
- package/dist/src/canvas/input.js +132 -0
- package/dist/src/canvas/input.js.map +10 -0
- package/dist/src/canvas/layout.js +309 -0
- package/dist/src/canvas/layout.js.map +10 -0
- package/dist/src/canvas/paint.js +878 -0
- package/dist/src/canvas/paint.js.map +10 -0
- package/dist/src/canvas/renderer.js +276 -0
- package/dist/src/canvas/renderer.js.map +10 -0
- package/dist/src/canvas/text.js +118 -0
- package/dist/src/canvas/text.js.map +10 -0
- package/dist/src/canvas/types.js +2 -0
- package/dist/src/canvas/types.js.map +9 -0
- package/dist/src/canvas/utils.js +139 -0
- package/dist/src/canvas/utils.js.map +10 -0
- package/dist/src/dom/applicators/advanced-layout.js +111 -0
- package/dist/src/dom/applicators/advanced-layout.js.map +10 -0
- package/dist/src/dom/applicators/background.js +54 -0
- package/dist/src/dom/applicators/background.js.map +10 -0
- package/dist/src/dom/applicators/border.js +33 -0
- package/dist/src/dom/applicators/border.js.map +10 -0
- package/dist/src/dom/applicators/color.js +36 -0
- package/dist/src/dom/applicators/color.js.map +10 -0
- package/dist/src/dom/applicators/display.js +57 -0
- package/dist/src/dom/applicators/display.js.map +10 -0
- package/dist/src/dom/applicators/effects.js +89 -0
- package/dist/src/dom/applicators/effects.js.map +10 -0
- package/dist/src/dom/applicators/events.js +518 -0
- package/dist/src/dom/applicators/events.js.map +10 -0
- package/dist/src/dom/applicators/font.js +39 -0
- package/dist/src/dom/applicators/font.js.map +10 -0
- package/dist/src/dom/applicators/index.js +296 -0
- package/dist/src/dom/applicators/index.js.map +10 -0
- package/dist/src/dom/applicators/layout.js +86 -0
- package/dist/src/dom/applicators/layout.js.map +10 -0
- package/dist/src/dom/applicators/margin.js +32 -0
- package/dist/src/dom/applicators/margin.js.map +10 -0
- package/dist/src/dom/applicators/padding.js +35 -0
- package/dist/src/dom/applicators/padding.js.map +10 -0
- package/dist/src/dom/applicators/size.js +42 -0
- package/dist/src/dom/applicators/size.js.map +10 -0
- package/dist/src/dom/applicators/transform.js +92 -0
- package/dist/src/dom/applicators/transform.js.map +10 -0
- package/dist/src/dom/applicators/transition.js +66 -0
- package/dist/src/dom/applicators/transition.js.map +10 -0
- package/dist/src/dom/applicators/typography.js +87 -0
- package/dist/src/dom/applicators/typography.js.map +10 -0
- package/dist/src/dom/canvas/index.js +50 -0
- package/dist/src/dom/canvas/index.js.map +10 -0
- package/dist/src/dom/components/audio.js +48 -0
- package/dist/src/dom/components/audio.js.map +10 -0
- package/dist/src/dom/components/avatar.js +58 -0
- package/dist/src/dom/components/avatar.js.map +10 -0
- package/dist/src/dom/components/badge.js +55 -0
- package/dist/src/dom/components/badge.js.map +10 -0
- package/dist/src/dom/components/button.js +29 -0
- package/dist/src/dom/components/button.js.map +10 -0
- package/dist/src/dom/components/card.js +33 -0
- package/dist/src/dom/components/card.js.map +10 -0
- package/dist/src/dom/components/center.js +32 -0
- package/dist/src/dom/components/center.js.map +10 -0
- package/dist/src/dom/components/checkbox.js +54 -0
- package/dist/src/dom/components/checkbox.js.map +10 -0
- package/dist/src/dom/components/column.js +31 -0
- package/dist/src/dom/components/column.js.map +10 -0
- package/dist/src/dom/components/container.js +29 -0
- package/dist/src/dom/components/container.js.map +10 -0
- package/dist/src/dom/components/divider.js +45 -0
- package/dist/src/dom/components/divider.js.map +10 -0
- package/dist/src/dom/components/grid.js +44 -0
- package/dist/src/dom/components/grid.js.map +10 -0
- package/dist/src/dom/components/heading.js +47 -0
- package/dist/src/dom/components/heading.js.map +10 -0
- package/dist/src/dom/components/image.js +39 -0
- package/dist/src/dom/components/image.js.map +10 -0
- package/dist/src/dom/components/index.js +217 -0
- package/dist/src/dom/components/index.js.map +10 -0
- package/dist/src/dom/components/input.js +41 -0
- package/dist/src/dom/components/input.js.map +10 -0
- package/dist/src/dom/components/link.js +42 -0
- package/dist/src/dom/components/link.js.map +10 -0
- package/dist/src/dom/components/list.js +42 -0
- package/dist/src/dom/components/list.js.map +10 -0
- package/dist/src/dom/components/paragraph.js +35 -0
- package/dist/src/dom/components/paragraph.js.map +10 -0
- package/dist/src/dom/components/progressbar.js +57 -0
- package/dist/src/dom/components/progressbar.js.map +10 -0
- package/dist/src/dom/components/route.js +44 -0
- package/dist/src/dom/components/route.js.map +10 -0
- package/dist/src/dom/components/router.js +33 -0
- package/dist/src/dom/components/router.js.map +10 -0
- package/dist/src/dom/components/row.js +31 -0
- package/dist/src/dom/components/row.js.map +10 -0
- package/dist/src/dom/components/select.js +57 -0
- package/dist/src/dom/components/select.js.map +10 -0
- package/dist/src/dom/components/slider.js +48 -0
- package/dist/src/dom/components/slider.js.map +10 -0
- package/dist/src/dom/components/spacer.js +30 -0
- package/dist/src/dom/components/spacer.js.map +10 -0
- package/dist/src/dom/components/spinner.js +65 -0
- package/dist/src/dom/components/spinner.js.map +10 -0
- package/dist/src/dom/components/stack.js +45 -0
- package/dist/src/dom/components/stack.js.map +10 -0
- package/dist/src/dom/components/switch.js +83 -0
- package/dist/src/dom/components/switch.js.map +10 -0
- package/dist/src/dom/components/text.js +37 -0
- package/dist/src/dom/components/text.js.map +10 -0
- package/dist/src/dom/components/textarea.js +51 -0
- package/dist/src/dom/components/textarea.js.map +10 -0
- package/dist/src/dom/components/video.js +51 -0
- package/dist/src/dom/components/video.js.map +10 -0
- package/dist/src/dom/debug.js +170 -0
- package/dist/src/dom/debug.js.map +10 -0
- package/dist/src/dom/events.js +112 -0
- package/dist/src/dom/events.js.map +10 -0
- package/dist/src/dom/index.js +73 -0
- package/dist/src/dom/index.js.map +9 -0
- package/dist/src/dom/renderer.js +277 -0
- package/dist/src/dom/renderer.js.map +10 -0
- package/dist/src/index.js +89 -0
- package/dist/src/index.js.map +9 -0
- package/package.json +84 -0
- package/src/canvas/QUICKSTART.md +421 -0
- package/src/canvas/README.md +376 -0
- package/src/canvas/accessibility.ts +218 -0
- package/src/canvas/events.ts +307 -0
- package/src/canvas/index.ts +35 -0
- package/src/canvas/input.ts +210 -0
- package/src/canvas/layout.ts +401 -0
- package/src/canvas/paint.ts +1321 -0
- package/src/canvas/renderer.ts +422 -0
- package/src/canvas/text.ts +182 -0
- package/src/canvas/types.ts +137 -0
- package/src/canvas/utils.ts +218 -0
- package/src/dom/README.md +265 -0
- package/src/dom/applicators/advanced-layout.ts +128 -0
- package/src/dom/applicators/background.ts +50 -0
- package/src/dom/applicators/border.ts +19 -0
- package/src/dom/applicators/color.ts +23 -0
- package/src/dom/applicators/display.ts +54 -0
- package/src/dom/applicators/effects.ts +97 -0
- package/src/dom/applicators/events.ts +689 -0
- package/src/dom/applicators/font.ts +27 -0
- package/src/dom/applicators/index.ts +354 -0
- package/src/dom/applicators/layout.ts +92 -0
- package/src/dom/applicators/margin.ts +18 -0
- package/src/dom/applicators/padding.ts +18 -0
- package/src/dom/applicators/size.ts +31 -0
- package/src/dom/applicators/transform.ts +93 -0
- package/src/dom/applicators/transition.ts +65 -0
- package/src/dom/applicators/typography.ts +91 -0
- package/src/dom/canvas/index.ts +60 -0
- package/src/dom/components/audio.ts +45 -0
- package/src/dom/components/avatar.ts +49 -0
- package/src/dom/components/badge.ts +45 -0
- package/src/dom/components/button.ts +13 -0
- package/src/dom/components/card.ts +19 -0
- package/src/dom/components/center.ts +16 -0
- package/src/dom/components/checkbox.ts +54 -0
- package/src/dom/components/column.ts +15 -0
- package/src/dom/components/container.ts +13 -0
- package/src/dom/components/divider.ts +37 -0
- package/src/dom/components/grid.ts +40 -0
- package/src/dom/components/heading.ts +41 -0
- package/src/dom/components/image.ts +27 -0
- package/src/dom/components/index.ts +115 -0
- package/src/dom/components/input.ts +29 -0
- package/src/dom/components/link.ts +35 -0
- package/src/dom/components/list.ts +30 -0
- package/src/dom/components/paragraph.ts +23 -0
- package/src/dom/components/progressbar.ts +51 -0
- package/src/dom/components/route.ts +37 -0
- package/src/dom/components/router.ts +22 -0
- package/src/dom/components/row.ts +15 -0
- package/src/dom/components/select.ts +56 -0
- package/src/dom/components/slider.ts +45 -0
- package/src/dom/components/spacer.ts +16 -0
- package/src/dom/components/spinner.ts +60 -0
- package/src/dom/components/stack.ts +34 -0
- package/src/dom/components/switch.ts +86 -0
- package/src/dom/components/text.ts +24 -0
- package/src/dom/components/textarea.ts +50 -0
- package/src/dom/components/video.ts +50 -0
- package/src/dom/debug.ts +247 -0
- package/src/dom/events.ts +168 -0
- package/src/dom/index.ts +11 -0
- package/src/dom/renderer.ts +327 -0
- package/src/index.ts +56 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ComponentHandler } from "./index.js";
|
|
6
|
+
|
|
7
|
+
export const inputHandler: ComponentHandler = {
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const el = document.createElement("input");
|
|
10
|
+
el.dataset.hypenType = "input";
|
|
11
|
+
return el as any as HTMLElement;
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
applyProps(el: HTMLElement, props: Record<string, any>): void {
|
|
15
|
+
const input = el as HTMLInputElement;
|
|
16
|
+
|
|
17
|
+
if (props.type !== undefined) {
|
|
18
|
+
input.type = String(props.type);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (props.placeholder !== undefined) {
|
|
22
|
+
input.placeholder = String(props.placeholder);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (props.value !== undefined) {
|
|
26
|
+
input.value = String(props.value);
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Link Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ComponentHandler } from "./index.js";
|
|
6
|
+
|
|
7
|
+
export const linkHandler: ComponentHandler = {
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const el = document.createElement("a");
|
|
10
|
+
el.dataset.hypenType = "link";
|
|
11
|
+
return el;
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
applyProps(el: HTMLElement, props: Record<string, any>): void {
|
|
15
|
+
const anchor = el as HTMLAnchorElement;
|
|
16
|
+
|
|
17
|
+
// Support href or first positional argument
|
|
18
|
+
const href = props["0"] || props.href;
|
|
19
|
+
if (href !== undefined) {
|
|
20
|
+
anchor.href = String(href);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Target property
|
|
24
|
+
if (props.target !== undefined) {
|
|
25
|
+
anchor.target = String(props.target);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Rel property
|
|
29
|
+
if (props.rel !== undefined) {
|
|
30
|
+
anchor.rel = String(props.rel);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List Component - Scrollable stack
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ComponentHandler } from "./index.js";
|
|
6
|
+
|
|
7
|
+
export const listHandler: ComponentHandler = {
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const el = document.createElement("div");
|
|
10
|
+
el.style.display = "flex";
|
|
11
|
+
el.style.overflow = "auto";
|
|
12
|
+
el.dataset.hypenType = "list";
|
|
13
|
+
return el;
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
applyProps(el: HTMLElement, props: Record<string, any>): void {
|
|
17
|
+
// Direction: vertical or horizontal
|
|
18
|
+
const direction = props.direction || props["1"] || "vertical";
|
|
19
|
+
if (direction === "vertical") {
|
|
20
|
+
el.style.flexDirection = "column";
|
|
21
|
+
} else {
|
|
22
|
+
el.style.flexDirection = "row";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Gap between items
|
|
26
|
+
if (props.gap !== undefined) {
|
|
27
|
+
el.style.gap = typeof props.gap === "number" ? `${props.gap}px` : String(props.gap);
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paragraph Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ComponentHandler } from "./index.js";
|
|
6
|
+
|
|
7
|
+
export const paragraphHandler: ComponentHandler = {
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const el = document.createElement("p");
|
|
10
|
+
el.dataset.hypenType = "paragraph";
|
|
11
|
+
return el;
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
applyProps(el: HTMLElement, props: Record<string, any>): void {
|
|
15
|
+
// Text content
|
|
16
|
+
const text = props["0"] || props.text;
|
|
17
|
+
if (text !== undefined) {
|
|
18
|
+
el.textContent = String(text);
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProgressBar Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ComponentHandler } from "./index.js";
|
|
6
|
+
|
|
7
|
+
export const progressBarHandler: ComponentHandler = {
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const wrapper = document.createElement("div");
|
|
10
|
+
wrapper.dataset.hypenType = "progressbar";
|
|
11
|
+
wrapper.style.width = "100%";
|
|
12
|
+
wrapper.style.height = "8px";
|
|
13
|
+
wrapper.style.backgroundColor = "#e0e0e0";
|
|
14
|
+
wrapper.style.borderRadius = "4px";
|
|
15
|
+
wrapper.style.overflow = "hidden";
|
|
16
|
+
|
|
17
|
+
const bar = document.createElement("div");
|
|
18
|
+
bar.dataset.hypenBar = "true";
|
|
19
|
+
bar.style.height = "100%";
|
|
20
|
+
bar.style.backgroundColor = "#2196F3";
|
|
21
|
+
bar.style.transition = "width 0.3s ease";
|
|
22
|
+
bar.style.width = "0%";
|
|
23
|
+
|
|
24
|
+
wrapper.appendChild(bar);
|
|
25
|
+
return wrapper;
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
applyProps(el: HTMLElement, props: Record<string, any>): void {
|
|
29
|
+
const bar = el.querySelector('[data-hypen-bar="true"]') as HTMLElement;
|
|
30
|
+
if (!bar) return;
|
|
31
|
+
|
|
32
|
+
// Value and max
|
|
33
|
+
const value = Number(props.value || 0);
|
|
34
|
+
const max = Number(props.max || 100);
|
|
35
|
+
const percentage = Math.min(100, Math.max(0, (value / max) * 100));
|
|
36
|
+
bar.style.width = `${percentage}%`;
|
|
37
|
+
|
|
38
|
+
// Color
|
|
39
|
+
if (props.color !== undefined) {
|
|
40
|
+
bar.style.backgroundColor = String(props.color);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Height
|
|
44
|
+
if (props.height !== undefined) {
|
|
45
|
+
const height = typeof props.height === "number" ? `${props.height}px` : String(props.height);
|
|
46
|
+
el.style.height = height;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route Component - Lazy rendering container for route content
|
|
3
|
+
* Stores component name as metadata, Router handles actual rendering
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ComponentHandler } from "./index.js";
|
|
7
|
+
|
|
8
|
+
export const routeHandler: ComponentHandler = {
|
|
9
|
+
create(): HTMLElement {
|
|
10
|
+
const el = document.createElement("div");
|
|
11
|
+
el.style.display = "flex";
|
|
12
|
+
el.style.flexDirection = "column";
|
|
13
|
+
el.style.width = "100%";
|
|
14
|
+
el.dataset.hypenType = "route";
|
|
15
|
+
el.dataset.routeRendered = "false"; // Track if component has been rendered
|
|
16
|
+
return el;
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
applyProps(el: HTMLElement, props: Record<string, any>): void {
|
|
20
|
+
// Extract route path from props
|
|
21
|
+
const path = props.path || props["0"] || "/";
|
|
22
|
+
el.dataset.routePath = String(path);
|
|
23
|
+
|
|
24
|
+
// Check if this is a lazy route (has __lazy flag)
|
|
25
|
+
const isLazy = props.__lazy === true;
|
|
26
|
+
el.dataset.routeLazy = String(isLazy);
|
|
27
|
+
|
|
28
|
+
// Store component name - either from explicit prop or from lazy child
|
|
29
|
+
const componentName = props.component || props.__lazy_child;
|
|
30
|
+
if (componentName) {
|
|
31
|
+
el.dataset.routeComponent = String(componentName);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log(`🛣️ Route created: path="${path}", lazy=${isLazy}, component="${el.dataset.routeComponent || 'none'}"`);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Router Component - Container for routes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ComponentHandler } from "./index.js";
|
|
6
|
+
|
|
7
|
+
export const routerHandler: ComponentHandler = {
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const el = document.createElement("div");
|
|
10
|
+
el.style.display = "flex";
|
|
11
|
+
el.style.flexDirection = "column";
|
|
12
|
+
el.style.width = "100%";
|
|
13
|
+
el.dataset.hypenType = "router";
|
|
14
|
+
return el;
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
applyProps(el: HTMLElement, props: Record<string, any>): void {
|
|
18
|
+
// Router doesn't need special prop handling
|
|
19
|
+
// The routing logic is handled by the Router module
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Row Component - Horizontal Stack
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ComponentHandler } from "./index.js";
|
|
6
|
+
|
|
7
|
+
export const rowHandler: ComponentHandler = {
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const el = document.createElement("div");
|
|
10
|
+
el.style.display = "flex";
|
|
11
|
+
el.style.flexDirection = "row";
|
|
12
|
+
el.dataset.hypenType = "row";
|
|
13
|
+
return el;
|
|
14
|
+
},
|
|
15
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Select Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ComponentHandler } from "./index.js";
|
|
6
|
+
|
|
7
|
+
export const selectHandler: ComponentHandler = {
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const el = document.createElement("select");
|
|
10
|
+
el.dataset.hypenType = "select";
|
|
11
|
+
return el as any as HTMLElement;
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
applyProps(el: HTMLElement, props: Record<string, any>): void {
|
|
15
|
+
const select = el as HTMLSelectElement;
|
|
16
|
+
|
|
17
|
+
// Value property
|
|
18
|
+
if (props.value !== undefined) {
|
|
19
|
+
select.value = String(props.value);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Disabled
|
|
23
|
+
if (props.disabled !== undefined) {
|
|
24
|
+
select.disabled = Boolean(props.disabled);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Multiple
|
|
28
|
+
if (props.multiple !== undefined) {
|
|
29
|
+
select.multiple = Boolean(props.multiple);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Options array
|
|
33
|
+
if (props.options && Array.isArray(props.options)) {
|
|
34
|
+
// Clear existing options
|
|
35
|
+
select.innerHTML = "";
|
|
36
|
+
|
|
37
|
+
// Add new options
|
|
38
|
+
props.options.forEach((opt: any) => {
|
|
39
|
+
const option = document.createElement("option");
|
|
40
|
+
|
|
41
|
+
if (typeof opt === "string") {
|
|
42
|
+
option.value = opt;
|
|
43
|
+
option.textContent = opt;
|
|
44
|
+
} else if (typeof opt === "object") {
|
|
45
|
+
option.value = String(opt.value ?? opt.label ?? "");
|
|
46
|
+
option.textContent = String(opt.label ?? opt.value ?? "");
|
|
47
|
+
if (opt.disabled) option.disabled = true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
select.appendChild(option);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slider Component (Range Input)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ComponentHandler } from "./index.js";
|
|
6
|
+
|
|
7
|
+
export const sliderHandler: ComponentHandler = {
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const el = document.createElement("input");
|
|
10
|
+
el.type = "range";
|
|
11
|
+
el.dataset.hypenType = "slider";
|
|
12
|
+
return el as any as HTMLElement;
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
applyProps(el: HTMLElement, props: Record<string, any>): void {
|
|
16
|
+
const input = el as HTMLInputElement;
|
|
17
|
+
|
|
18
|
+
// Value
|
|
19
|
+
if (props.value !== undefined) {
|
|
20
|
+
input.value = String(props.value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Min
|
|
24
|
+
if (props.min !== undefined) {
|
|
25
|
+
input.min = String(props.min);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Max
|
|
29
|
+
if (props.max !== undefined) {
|
|
30
|
+
input.max = String(props.max);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Step
|
|
34
|
+
if (props.step !== undefined) {
|
|
35
|
+
input.step = String(props.step);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Disabled
|
|
39
|
+
if (props.disabled !== undefined) {
|
|
40
|
+
input.disabled = Boolean(props.disabled);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spacer Component - Flexible space in flex layouts
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ComponentHandler } from "./index.js";
|
|
6
|
+
|
|
7
|
+
export const spacerHandler: ComponentHandler = {
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const el = document.createElement("div");
|
|
10
|
+
el.style.flex = "1";
|
|
11
|
+
el.dataset.hypenType = "spacer";
|
|
12
|
+
return el;
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spinner Component (Loading Indicator)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ComponentHandler } from "./index.js";
|
|
6
|
+
|
|
7
|
+
export const spinnerHandler: ComponentHandler = {
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const wrapper = document.createElement("div");
|
|
10
|
+
wrapper.dataset.hypenType = "spinner";
|
|
11
|
+
wrapper.style.display = "inline-block";
|
|
12
|
+
|
|
13
|
+
const spinner = document.createElement("div");
|
|
14
|
+
spinner.style.width = "40px";
|
|
15
|
+
spinner.style.height = "40px";
|
|
16
|
+
spinner.style.border = "4px solid #f3f3f3";
|
|
17
|
+
spinner.style.borderTop = "4px solid #3498db";
|
|
18
|
+
spinner.style.borderRadius = "50%";
|
|
19
|
+
spinner.style.animation = "spin 1s linear infinite";
|
|
20
|
+
|
|
21
|
+
// Add keyframe animation
|
|
22
|
+
const style = document.createElement("style");
|
|
23
|
+
style.textContent = `
|
|
24
|
+
@keyframes spin {
|
|
25
|
+
0% { transform: rotate(0deg); }
|
|
26
|
+
100% { transform: rotate(360deg); }
|
|
27
|
+
}
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
wrapper.appendChild(style);
|
|
31
|
+
wrapper.appendChild(spinner);
|
|
32
|
+
|
|
33
|
+
return wrapper;
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
applyProps(el: HTMLElement, props: Record<string, any>): void {
|
|
37
|
+
const spinner = el.querySelector("div:not(style)") as HTMLElement;
|
|
38
|
+
if (!spinner) return;
|
|
39
|
+
|
|
40
|
+
// Size
|
|
41
|
+
if (props.size !== undefined) {
|
|
42
|
+
const size = String(props.size);
|
|
43
|
+
const sizeMap: Record<string, string> = {
|
|
44
|
+
small: "24px",
|
|
45
|
+
medium: "40px",
|
|
46
|
+
large: "60px",
|
|
47
|
+
};
|
|
48
|
+
const actualSize = sizeMap[size] || size;
|
|
49
|
+
spinner.style.width = actualSize;
|
|
50
|
+
spinner.style.height = actualSize;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Color
|
|
54
|
+
if (props.color !== undefined) {
|
|
55
|
+
spinner.style.borderTopColor = String(props.color);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stack Component - Overlaying elements with absolute positioning
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ComponentHandler } from "./index.js";
|
|
6
|
+
|
|
7
|
+
export const stackHandler: ComponentHandler = {
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const el = document.createElement("div");
|
|
10
|
+
el.style.position = "relative";
|
|
11
|
+
el.style.display = "flex";
|
|
12
|
+
el.dataset.hypenType = "stack";
|
|
13
|
+
|
|
14
|
+
// Children will be absolutely positioned
|
|
15
|
+
const style = document.createElement("style");
|
|
16
|
+
style.textContent = `
|
|
17
|
+
[data-hypen-type="stack"] > * {
|
|
18
|
+
position: absolute;
|
|
19
|
+
top: 0;
|
|
20
|
+
left: 0;
|
|
21
|
+
width: 100%;
|
|
22
|
+
height: 100%;
|
|
23
|
+
}
|
|
24
|
+
[data-hypen-type="stack"] > *:first-child {
|
|
25
|
+
position: relative;
|
|
26
|
+
}
|
|
27
|
+
`;
|
|
28
|
+
el.appendChild(style);
|
|
29
|
+
|
|
30
|
+
return el;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Switch Component (Toggle)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ComponentHandler } from "./index.js";
|
|
6
|
+
|
|
7
|
+
export const switchHandler: ComponentHandler = {
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const wrapper = document.createElement("label");
|
|
10
|
+
wrapper.dataset.hypenType = "switch";
|
|
11
|
+
wrapper.style.display = "inline-flex";
|
|
12
|
+
wrapper.style.alignItems = "center";
|
|
13
|
+
wrapper.style.gap = "8px";
|
|
14
|
+
wrapper.style.cursor = "pointer";
|
|
15
|
+
|
|
16
|
+
const input = document.createElement("input");
|
|
17
|
+
input.type = "checkbox";
|
|
18
|
+
input.dataset.hypenSwitch = "true";
|
|
19
|
+
|
|
20
|
+
// Style the switch
|
|
21
|
+
input.style.appearance = "none";
|
|
22
|
+
input.style.width = "44px";
|
|
23
|
+
input.style.height = "24px";
|
|
24
|
+
input.style.backgroundColor = "#ccc";
|
|
25
|
+
input.style.borderRadius = "12px";
|
|
26
|
+
input.style.position = "relative";
|
|
27
|
+
input.style.cursor = "pointer";
|
|
28
|
+
input.style.transition = "background-color 0.2s";
|
|
29
|
+
|
|
30
|
+
// Add pseudo-element styling via CSS
|
|
31
|
+
const style = document.createElement("style");
|
|
32
|
+
style.textContent = `
|
|
33
|
+
input[data-hypen-switch="true"]::before {
|
|
34
|
+
content: "";
|
|
35
|
+
position: absolute;
|
|
36
|
+
width: 20px;
|
|
37
|
+
height: 20px;
|
|
38
|
+
background-color: white;
|
|
39
|
+
border-radius: 50%;
|
|
40
|
+
top: 2px;
|
|
41
|
+
left: 2px;
|
|
42
|
+
transition: transform 0.2s;
|
|
43
|
+
}
|
|
44
|
+
input[data-hypen-switch="true"]:checked {
|
|
45
|
+
background-color: #4CAF50;
|
|
46
|
+
}
|
|
47
|
+
input[data-hypen-switch="true"]:checked::before {
|
|
48
|
+
transform: translateX(20px);
|
|
49
|
+
}
|
|
50
|
+
`;
|
|
51
|
+
wrapper.appendChild(style);
|
|
52
|
+
wrapper.appendChild(input);
|
|
53
|
+
|
|
54
|
+
return wrapper;
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
applyProps(el: HTMLElement, props: Record<string, any>): void {
|
|
58
|
+
const input = el.querySelector('input[type="checkbox"]') as HTMLInputElement;
|
|
59
|
+
if (!input) return;
|
|
60
|
+
|
|
61
|
+
// On state (checked)
|
|
62
|
+
if (props.on !== undefined) {
|
|
63
|
+
input.checked = Boolean(props.on);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Disabled
|
|
67
|
+
if (props.disabled !== undefined) {
|
|
68
|
+
input.disabled = Boolean(props.disabled);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Label text
|
|
72
|
+
const label = props["0"] || props.label;
|
|
73
|
+
if (label !== undefined) {
|
|
74
|
+
// Remove existing text node if any
|
|
75
|
+
const textNodes = Array.from(el.childNodes).filter(
|
|
76
|
+
node => node.nodeType === Node.TEXT_NODE
|
|
77
|
+
);
|
|
78
|
+
textNodes.forEach(node => node.remove());
|
|
79
|
+
|
|
80
|
+
// Add new label text
|
|
81
|
+
el.appendChild(document.createTextNode(String(label)));
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ComponentHandler } from "./index.js";
|
|
6
|
+
|
|
7
|
+
export const textHandler: ComponentHandler = {
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const el = document.createElement("span");
|
|
10
|
+
el.style.display = "inline-block";
|
|
11
|
+
el.dataset.hypenType = "text";
|
|
12
|
+
return el;
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
applyProps(el: HTMLElement, props: Record<string, any>): void {
|
|
16
|
+
// Text content from first positional arg or "text" prop
|
|
17
|
+
const text = props["0"] || props.text;
|
|
18
|
+
if (text !== undefined) {
|
|
19
|
+
// Store the original text template for state interpolation
|
|
20
|
+
el.dataset.textTemplate = String(text);
|
|
21
|
+
el.textContent = String(text);
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Textarea Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ComponentHandler } from "./index.js";
|
|
6
|
+
|
|
7
|
+
export const textareaHandler: ComponentHandler = {
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const el = document.createElement("textarea");
|
|
10
|
+
el.dataset.hypenType = "textarea";
|
|
11
|
+
return el as any as HTMLElement;
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
applyProps(el: HTMLElement, props: Record<string, any>): void {
|
|
15
|
+
const textarea = el as HTMLTextAreaElement;
|
|
16
|
+
|
|
17
|
+
// Value property
|
|
18
|
+
const value = props["0"] || props.value;
|
|
19
|
+
if (value !== undefined) {
|
|
20
|
+
textarea.value = String(value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Placeholder
|
|
24
|
+
if (props.placeholder !== undefined) {
|
|
25
|
+
textarea.placeholder = String(props.placeholder);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Rows
|
|
29
|
+
if (props.rows !== undefined) {
|
|
30
|
+
textarea.rows = Number(props.rows);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Cols
|
|
34
|
+
if (props.cols !== undefined) {
|
|
35
|
+
textarea.cols = Number(props.cols);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Disabled
|
|
39
|
+
if (props.disabled !== undefined) {
|
|
40
|
+
textarea.disabled = Boolean(props.disabled);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Readonly
|
|
44
|
+
if (props.readonly !== undefined) {
|
|
45
|
+
textarea.readOnly = Boolean(props.readonly);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Video Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ComponentHandler } from "./index.js";
|
|
6
|
+
|
|
7
|
+
export const videoHandler: ComponentHandler = {
|
|
8
|
+
create(): HTMLElement {
|
|
9
|
+
const el = document.createElement("video");
|
|
10
|
+
el.dataset.hypenType = "video";
|
|
11
|
+
return el as any as HTMLElement;
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
applyProps(el: HTMLElement, props: Record<string, any>): void {
|
|
15
|
+
const video = el as HTMLVideoElement;
|
|
16
|
+
|
|
17
|
+
// Source
|
|
18
|
+
const src = props["0"] || props.src;
|
|
19
|
+
if (src !== undefined) {
|
|
20
|
+
video.src = String(src);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Controls
|
|
24
|
+
if (props.controls !== undefined) {
|
|
25
|
+
video.controls = Boolean(props.controls);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Autoplay
|
|
29
|
+
if (props.autoplay !== undefined) {
|
|
30
|
+
video.autoplay = Boolean(props.autoplay);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Loop
|
|
34
|
+
if (props.loop !== undefined) {
|
|
35
|
+
video.loop = Boolean(props.loop);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Muted
|
|
39
|
+
if (props.muted !== undefined) {
|
|
40
|
+
video.muted = Boolean(props.muted);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Poster
|
|
44
|
+
if (props.poster !== undefined) {
|
|
45
|
+
video.poster = String(props.poster);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
|