juxscript 1.0.68 → 1.0.69
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/bin/cli.js +1 -1
- package/create/index.jux +79 -0
- package/create/layout.jux +38 -18
- package/lib/componentsv2/element/engine.js +17 -2
- package/lib/componentsv2/element/engine.js.map +1 -1
- package/lib/componentsv2/element/engine.ts +21 -2
- package/lib/componentsv2/element/skin.js +28 -26
- package/lib/componentsv2/element/skin.js.map +1 -1
- package/lib/componentsv2/element/skin.ts +31 -27
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ const command = process.argv[2];
|
|
|
11
11
|
function scaffoldProject(targetDir, packageRoot) {
|
|
12
12
|
const juxDir = path.join(targetDir, 'jux');
|
|
13
13
|
if (!fs.existsSync(juxDir)) fs.mkdirSync(juxDir, { recursive: true });
|
|
14
|
-
const presetDir = path.join(packageRoot, '
|
|
14
|
+
const presetDir = path.join(packageRoot, 'create');
|
|
15
15
|
let copied = 0;
|
|
16
16
|
if (fs.existsSync(presetDir)) {
|
|
17
17
|
fs.readdirSync(presetDir).forEach(file => {
|
package/create/index.jux
CHANGED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Element, List } from 'juxscript';
|
|
2
|
+
import { LandingLayout } from './layout.jux';
|
|
3
|
+
|
|
4
|
+
// 1. Initialize Layout
|
|
5
|
+
LandingLayout();
|
|
6
|
+
|
|
7
|
+
// 2. Header: Logo & Nav
|
|
8
|
+
Element('logo', { tag: 'h2' })
|
|
9
|
+
.text('JUX STUDIO')
|
|
10
|
+
.style('margin: 0; padding: 25px 40px; font-weight: 800; letter-spacing: -1px; display: flex; align-items: center; height: 100%; box-sizing: border-box;')
|
|
11
|
+
.render('landing-layout-0-0');
|
|
12
|
+
|
|
13
|
+
// 3. Hero Section
|
|
14
|
+
const hero = Element('hero-content', { tag: 'div' })
|
|
15
|
+
.style('display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100%; text-align: center; background: #f8f9fa;')
|
|
16
|
+
.render('landing-layout-1-0');
|
|
17
|
+
|
|
18
|
+
Element('hero-h1', { tag: 'h1' })
|
|
19
|
+
.text('Build Web Apps. Simply.')
|
|
20
|
+
.style('font-size: 4rem; margin: 0 0 20px 0; font-weight: 900; background: linear-gradient(45deg, #111, #555); -webkit-background-clip: text; -webkit-text-fill-color: transparent;')
|
|
21
|
+
.render('hero-content');
|
|
22
|
+
|
|
23
|
+
Element('hero-p', { tag: 'p' })
|
|
24
|
+
.text('No complex build steps. No magic. Just standard JavaScript components.')
|
|
25
|
+
.style('font-size: 1.5rem; color: #666; max-width: 600px; line-height: 1.5;')
|
|
26
|
+
.render('hero-content');
|
|
27
|
+
|
|
28
|
+
Element('cta-btn', { tag: 'button' })
|
|
29
|
+
.text('Get Started')
|
|
30
|
+
.style('margin-top: 30px; padding: 15px 40px; font-size: 1.2rem; background: #000; color: #fff; border: none; border-radius: 50px; cursor: pointer; transition: transform 0.2s;')
|
|
31
|
+
.render('hero-content');
|
|
32
|
+
|
|
33
|
+
// 4. Features Section (Nested Content)
|
|
34
|
+
const featuresWrapper = Element('features-wrapper', { tag: 'div' })
|
|
35
|
+
.style('max-width: 1000px; margin: 60px auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 40px; padding: 0 20px;')
|
|
36
|
+
.render('landing-layout-2-0');
|
|
37
|
+
|
|
38
|
+
// Component Showcase within Landing Page
|
|
39
|
+
const listDemo = Element('list-demo-card', { tag: 'div' })
|
|
40
|
+
.style('background: #fff; padding: 30px; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.05); border: 1px solid #eee;')
|
|
41
|
+
.render('features-wrapper');
|
|
42
|
+
|
|
43
|
+
Element('list-label', { tag: 'h3' }).text('Interactive Components').render('list-demo-card');
|
|
44
|
+
Element('list-desc', { tag: 'p' }).text('Try dragging or sorting this list:').style('color:#666; margin-bottom:20px;').render('list-demo-card');
|
|
45
|
+
|
|
46
|
+
List('feature-list', { items: ['Zero Config', 'Standard Web APIs', 'Hot Reloading', 'TypeScript Ready'] })
|
|
47
|
+
.listType('unordered')
|
|
48
|
+
.enableSort()
|
|
49
|
+
.enableMove()
|
|
50
|
+
.render('list-demo-card');
|
|
51
|
+
|
|
52
|
+
// Feature Cards
|
|
53
|
+
const cardData = [
|
|
54
|
+
{ title: 'Reactive State', desc: 'Granular binding means high performance without a virtual DOM.' },
|
|
55
|
+
{ title: 'Time Travel', desc: 'Built-in undo/redo capabilities for complex application state.' }
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
cardData.forEach((f, i) => {
|
|
59
|
+
const card = Element(`card-${i}`, { tag: 'div' })
|
|
60
|
+
.style('background: #fff; padding: 30px; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.05); border: 1px solid #eee;')
|
|
61
|
+
.render('features-wrapper');
|
|
62
|
+
|
|
63
|
+
Element(`ct-${i}`, { tag: 'h3' }).text(f.title).render(`card-${i}`);
|
|
64
|
+
Element(`cd-${i}`, { tag: 'p' }).text(f.desc).style('color: #666; line-height: 1.6;').render(`card-${i}`);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// 5. Footer
|
|
68
|
+
const footer = Element('footer-content', { tag: 'div' })
|
|
69
|
+
.html('© 2025 Jux Project. Open Source.')
|
|
70
|
+
.style('display:flex; align-items:center; justify-content:center; height:100%; background:#111; color:#666;')
|
|
71
|
+
.render('landing-layout-3-0');
|
|
72
|
+
|
|
73
|
+
// 6. Global Reset (injected via Element)
|
|
74
|
+
Element('reset-styles', { tag: 'style' })
|
|
75
|
+
.text(`
|
|
76
|
+
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background: #fff; color: #111; }
|
|
77
|
+
button:hover { transform: scale(1.05); }
|
|
78
|
+
`)
|
|
79
|
+
.render('app');
|
package/create/layout.jux
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Element, Grid } from 'juxscript';
|
|
2
2
|
|
|
3
3
|
// Import the layout styles - testing link.
|
|
4
4
|
|
|
@@ -9,32 +9,35 @@ import { jux } from 'juxscript';
|
|
|
9
9
|
// Note: #app is automatically created by the Jux compiler
|
|
10
10
|
|
|
11
11
|
export function initializeGrid() {
|
|
12
|
-
// add the base jux styles
|
|
13
|
-
|
|
12
|
+
// add the base jux styles via style tag import
|
|
13
|
+
Element('layout-css', { tag: 'style' })
|
|
14
|
+
.text('@import "style.css";')
|
|
15
|
+
.render(document.head);
|
|
16
|
+
|
|
14
17
|
// Header area
|
|
15
|
-
const appHeader =
|
|
16
|
-
const appHeaderContent =
|
|
17
|
-
const appHeaderLogo =
|
|
18
|
-
const appHeaderNav =
|
|
19
|
-
const appHeaderActions =
|
|
18
|
+
const appHeader = Element('appheader').render('#app');
|
|
19
|
+
const appHeaderContent = Element('appheader-content').render('#appheader');
|
|
20
|
+
const appHeaderLogo = Element('appheader-logo').render('#appheader-content');
|
|
21
|
+
const appHeaderNav = Element('appheader-nav').render('#appheader-content');
|
|
22
|
+
const appHeaderActions = Element('appheader-actions').render('#appheader-content');
|
|
20
23
|
|
|
21
24
|
// Left sidebar
|
|
22
|
-
const appAside =
|
|
25
|
+
const appAside = Element('appaside').render('#app');
|
|
23
26
|
|
|
24
27
|
// Main content area
|
|
25
|
-
const appMain =
|
|
26
|
-
const appMainContent =
|
|
28
|
+
const appMain = Element('appmain').render('#app');
|
|
29
|
+
const appMainContent = Element('appmain-content').render('#appmain');
|
|
27
30
|
|
|
28
31
|
// Right sidebar (optional - starts hidden)
|
|
29
|
-
const appSidebar =
|
|
30
|
-
const appSidebarHeader =
|
|
31
|
-
const appSidebarContent =
|
|
32
|
-
const appSidebarFooter =
|
|
32
|
+
const appSidebar = Element('appsidebar').render('#app');
|
|
33
|
+
const appSidebarHeader = Element('appsidebar-header').render('#appsidebar');
|
|
34
|
+
const appSidebarContent = Element('appsidebar-content').render('#appsidebar');
|
|
35
|
+
const appSidebarFooter = Element('appsidebar-footer').render('#appsidebar');
|
|
33
36
|
|
|
34
37
|
// Footer area
|
|
35
|
-
const appFooter =
|
|
36
|
-
const appFooterContent =
|
|
37
|
-
const appFooterLegal =
|
|
38
|
+
const appFooter = Element('appfooter').render('#app');
|
|
39
|
+
const appFooterContent = Element('appfooter-content').render('#appfooter');
|
|
40
|
+
const appFooterLegal = Element('appfooter-legal').render('#appfooter-content');
|
|
38
41
|
|
|
39
42
|
// Return references to all containers
|
|
40
43
|
return {
|
|
@@ -54,4 +57,21 @@ export function initializeGrid() {
|
|
|
54
57
|
appFooterContent,
|
|
55
58
|
appFooterLegal
|
|
56
59
|
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function LandingLayout() {
|
|
63
|
+
// Defines a classic landing page structure using JUX Grid
|
|
64
|
+
return Grid('landing-layout')
|
|
65
|
+
.rows([
|
|
66
|
+
{ size: '80px', class: 'layout-header' }, // 0: Navbar
|
|
67
|
+
{ size: '500px', class: 'layout-hero' }, // 1: Hero Banner
|
|
68
|
+
{ size: 'auto', class: 'layout-features' }, // 2: Main Content
|
|
69
|
+
{ size: '120px', class: 'layout-footer' } // 3: Footer
|
|
70
|
+
])
|
|
71
|
+
.columns([
|
|
72
|
+
{ size: '1fr', class: 'col-main' } // Single column flow
|
|
73
|
+
])
|
|
74
|
+
.gap('0px')
|
|
75
|
+
.width('100%')
|
|
76
|
+
.render('app');
|
|
57
77
|
}
|
|
@@ -13,13 +13,14 @@ export class ElementEngine extends BaseEngine {
|
|
|
13
13
|
attributes: {},
|
|
14
14
|
tagName: options.tag || 'div',
|
|
15
15
|
content: options.html || options.text || '',
|
|
16
|
-
contentType: options.html ? 'html' : 'text'
|
|
16
|
+
contentType: options.html ? 'html' : 'text',
|
|
17
|
+
inlineStyle: options.style || '' // ✅ Initialize logic
|
|
17
18
|
};
|
|
18
19
|
}
|
|
19
20
|
/**
|
|
20
21
|
* Set the HTML Tag name (e.g. 'div', 'span', 'h1', 'section')
|
|
21
22
|
*/
|
|
22
|
-
|
|
23
|
+
tag(tagName) {
|
|
23
24
|
this.updateState({ tagName });
|
|
24
25
|
this.emit('config', { tagName });
|
|
25
26
|
return this;
|
|
@@ -47,5 +48,19 @@ export class ElementEngine extends BaseEngine {
|
|
|
47
48
|
this.updateState({ content: current + value, contentType: 'html' });
|
|
48
49
|
return this;
|
|
49
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Set inline styles (e.g. "color: red; margin: 10px;")
|
|
53
|
+
*/
|
|
54
|
+
style(cssText) {
|
|
55
|
+
this.updateState({ inlineStyle: cssText });
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Receives RAW DOM events from the Skin and emits them to listeners.
|
|
60
|
+
* This keeps the 'emit' logic encapsulated within the Engine.
|
|
61
|
+
*/
|
|
62
|
+
handleEvent(eventName, content) {
|
|
63
|
+
this.emit(eventName, content);
|
|
64
|
+
}
|
|
50
65
|
}
|
|
51
66
|
//# sourceMappingURL=engine.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.js","sourceRoot":"","sources":["engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,uBAAuB,CAAC;AAiB9D,MAAM,OAAO,aAAc,SAAQ,UAAwC;IACvE,YAAY,EAAU,EAAE,UAA0B,EAAE;QAChD,KAAK,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IACvB,CAAC;IAES,YAAY,CAAC,EAAU,EAAE,OAAuB;QACtD,OAAO;YACH,EAAE;YACF,OAAO,EAAE,CAAC,aAAa,CAAC;YACxB,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,KAAK;YACf,OAAO,EAAE,KAAK;YACd,UAAU,EAAE,EAAE;YACd,OAAO,EAAE,OAAO,CAAC,GAAG,IAAI,KAAK;YAC7B,OAAO,EAAE,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,IAAI,EAAE;YAC3C,WAAW,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;YAC3C,WAAW,EAAE,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,qBAAqB;SACzD,CAAC;IACN,CAAC;IAED;;OAEG;IACH,GAAG,CAAC,OAAe;QACf,IAAI,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QACjC,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;OAEG;IACH,IAAI,CAAC,KAAa;QACd,IAAI,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC;QAC1D,8CAA8C;QAC9C,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;OAEG;IACH,IAAI,CAAC,KAAa;QACd,IAAI,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC;QAC1D,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;OAEG;IACH,UAAU,CAAC,KAAa;QACpB,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC;QACzC,IAAI,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,OAAO,GAAG,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC;QACpE,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAe;QACjB,IAAI,CAAC,WAAW,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;;OAGG;IACH,WAAW,CAAC,SAAiB,EAAE,OAAY;QACvC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAClC,CAAC;CACJ"}
|
|
@@ -4,6 +4,7 @@ export interface ElementState extends BaseState {
|
|
|
4
4
|
tagName: string;
|
|
5
5
|
content: string | null;
|
|
6
6
|
contentType: 'text' | 'html';
|
|
7
|
+
inlineStyle: string; // ✅ Added inlineStyle to state
|
|
7
8
|
// BaseState provides: classes, attributes, visible, etc.
|
|
8
9
|
}
|
|
9
10
|
|
|
@@ -11,6 +12,7 @@ export interface ElementOptions {
|
|
|
11
12
|
tag?: string;
|
|
12
13
|
text?: string;
|
|
13
14
|
html?: string;
|
|
15
|
+
style?: string; // ✅ Added style option
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export class ElementEngine extends BaseEngine<ElementState, ElementOptions> {
|
|
@@ -28,14 +30,15 @@ export class ElementEngine extends BaseEngine<ElementState, ElementOptions> {
|
|
|
28
30
|
attributes: {},
|
|
29
31
|
tagName: options.tag || 'div',
|
|
30
32
|
content: options.html || options.text || '',
|
|
31
|
-
contentType: options.html ? 'html' : 'text'
|
|
33
|
+
contentType: options.html ? 'html' : 'text',
|
|
34
|
+
inlineStyle: options.style || '' // ✅ Initialize logic
|
|
32
35
|
};
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
/**
|
|
36
39
|
* Set the HTML Tag name (e.g. 'div', 'span', 'h1', 'section')
|
|
37
40
|
*/
|
|
38
|
-
|
|
41
|
+
tag(tagName: string): this {
|
|
39
42
|
this.updateState({ tagName });
|
|
40
43
|
this.emit('config', { tagName });
|
|
41
44
|
return this;
|
|
@@ -66,4 +69,20 @@ export class ElementEngine extends BaseEngine<ElementState, ElementOptions> {
|
|
|
66
69
|
this.updateState({ content: current + value, contentType: 'html' });
|
|
67
70
|
return this;
|
|
68
71
|
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Set inline styles (e.g. "color: red; margin: 10px;")
|
|
75
|
+
*/
|
|
76
|
+
style(cssText: string): this {
|
|
77
|
+
this.updateState({ inlineStyle: cssText });
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Receives RAW DOM events from the Skin and emits them to listeners.
|
|
83
|
+
* This keeps the 'emit' logic encapsulated within the Engine.
|
|
84
|
+
*/
|
|
85
|
+
handleEvent(eventName: string, content: any): void {
|
|
86
|
+
this.emit(eventName, content);
|
|
87
|
+
}
|
|
69
88
|
}
|
|
@@ -6,45 +6,47 @@ export class ElementSkin extends BaseSkin {
|
|
|
6
6
|
get structureCss() {
|
|
7
7
|
return new URL('./structure.css', import.meta.url).href;
|
|
8
8
|
}
|
|
9
|
-
|
|
10
|
-
* Defines the initial root element creation strategy
|
|
11
|
-
*/
|
|
9
|
+
// ✅ Fix: Use state to determine initial tag, default to div
|
|
12
10
|
createRoot() {
|
|
13
|
-
|
|
11
|
+
const tag = this.engine.state.tagName || 'div';
|
|
12
|
+
return document.createElement(tag);
|
|
14
13
|
}
|
|
15
14
|
bindEvents(root) {
|
|
16
|
-
//
|
|
17
|
-
//
|
|
15
|
+
// ✅ Forward common events to the engine via public handler
|
|
16
|
+
// This allows engine.on('click', ...) to work without skin accessing protected 'emit'
|
|
17
|
+
const events = ['click', 'mouseenter', 'mouseleave', 'input', 'change', 'focus', 'blur'];
|
|
18
|
+
events.forEach(eventName => {
|
|
19
|
+
root.addEventListener(eventName, (e) => {
|
|
20
|
+
this.engine.handleEvent(eventName, e);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
18
23
|
}
|
|
19
24
|
updateSkin(state) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// 1. Tag Name Reconciliation
|
|
23
|
-
// If the tag name changes, we must replace the root element entirely.
|
|
24
|
-
if (this.root.tagName.toLowerCase() !== state.tagName.toLowerCase()) {
|
|
25
|
+
// 1. Tag Replacement Logic
|
|
26
|
+
if (this.root && this.root.tagName.toLowerCase() !== state.tagName.toLowerCase()) {
|
|
25
27
|
const newRoot = document.createElement(state.tagName);
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
// Transfer attributes/classes/listeners logic is complex
|
|
29
|
+
// We rely on re-binding events and re-applying attributes for the new root.
|
|
30
|
+
if (this.root.parentNode) {
|
|
31
|
+
this.root.parentNode.replaceChild(newRoot, this.root);
|
|
29
32
|
}
|
|
30
|
-
this.root.replaceWith(newRoot);
|
|
31
33
|
this.root = newRoot;
|
|
32
|
-
// Re-bind
|
|
33
|
-
// if they were attached locally, but this is an edge case for "readonly" building blocks).
|
|
34
|
+
this.bindEvents(this.root); // Re-bind events to new root
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
+
if (!this.root)
|
|
37
|
+
return;
|
|
38
|
+
// 2. Base Attributes
|
|
36
39
|
this.applySkinAttributes(this.root, state);
|
|
37
|
-
// 3. Content
|
|
38
|
-
// We only touch the DOM if content differs to avoid stomping on interactive children
|
|
40
|
+
// 3. Content
|
|
39
41
|
if (state.contentType === 'html') {
|
|
40
|
-
|
|
41
|
-
this.root.innerHTML = state.content || '';
|
|
42
|
-
}
|
|
42
|
+
this.root.innerHTML = state.content || '';
|
|
43
43
|
}
|
|
44
44
|
else {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
this.root.textContent = state.content || '';
|
|
46
|
+
}
|
|
47
|
+
// 4. Inline Styles
|
|
48
|
+
if (state.inlineStyle) {
|
|
49
|
+
this.root.style.cssText = state.inlineStyle;
|
|
48
50
|
}
|
|
49
51
|
}
|
|
50
52
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skin.js","sourceRoot":"","sources":["skin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAG/C,MAAM,OAAO,WAAY,SAAQ,QAAqC;IAClE,YAAY,MAAqB;QAC7B,KAAK,CAAC,MAAM,CAAC,CAAC;IAClB,CAAC;IAED,IAAc,YAAY;QACtB,OAAO,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;IAC5D,CAAC;IAED
|
|
1
|
+
{"version":3,"file":"skin.js","sourceRoot":"","sources":["skin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAG/C,MAAM,OAAO,WAAY,SAAQ,QAAqC;IAClE,YAAY,MAAqB;QAC7B,KAAK,CAAC,MAAM,CAAC,CAAC;IAClB,CAAC;IAED,IAAc,YAAY;QACtB,OAAO,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;IAC5D,CAAC;IAED,4DAA4D;IAClD,UAAU;QAChB,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC;QAC/C,OAAO,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IACvC,CAAC;IAES,UAAU,CAAC,IAAiB;QAClC,2DAA2D;QAC3D,sFAAsF;QACtF,MAAM,MAAM,GAAG,CAAC,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QAEzF,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE;YACvB,IAAI,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE;gBACnC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;YAC1C,CAAC,CAAC,CAAC;QACP,CAAC,CAAC,CAAC;IACP,CAAC;IAES,UAAU,CAAC,KAAmB;QACpC,2BAA2B;QAC3B,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC;YAC/E,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAEtD,yDAAyD;YACzD,4EAA4E;YAE5E,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;gBACvB,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YAC1D,CAAC;YAED,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC;YACpB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,6BAA6B;QAC7D,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,IAAI;YAAE,OAAO;QAEvB,qBAAqB;QACrB,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAE3C,aAAa;QACb,IAAI,KAAK,CAAC,WAAW,KAAK,MAAM,EAAE,CAAC;YAC/B,IAAI,CAAC,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC;QAC9C,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC;QAChD,CAAC;QAED,mBAAmB;QACnB,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;YACpB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,WAAW,CAAC;QAChD,CAAC;IACL,CAAC;CACJ"}
|
|
@@ -10,51 +10,55 @@ export class ElementSkin extends BaseSkin<ElementState, ElementEngine> {
|
|
|
10
10
|
return new URL('./structure.css', import.meta.url).href;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
* Defines the initial root element creation strategy
|
|
15
|
-
*/
|
|
13
|
+
// ✅ Fix: Use state to determine initial tag, default to div
|
|
16
14
|
protected createRoot(): HTMLElement {
|
|
17
|
-
|
|
15
|
+
const tag = this.engine.state.tagName || 'div';
|
|
16
|
+
return document.createElement(tag);
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
protected bindEvents(root: HTMLElement): void {
|
|
21
|
-
//
|
|
22
|
-
//
|
|
20
|
+
// ✅ Forward common events to the engine via public handler
|
|
21
|
+
// This allows engine.on('click', ...) to work without skin accessing protected 'emit'
|
|
22
|
+
const events = ['click', 'mouseenter', 'mouseleave', 'input', 'change', 'focus', 'blur'];
|
|
23
|
+
|
|
24
|
+
events.forEach(eventName => {
|
|
25
|
+
root.addEventListener(eventName, (e) => {
|
|
26
|
+
this.engine.handleEvent(eventName, e);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
protected updateSkin(state: ElementState): void {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
// 1. Tag Name Reconciliation
|
|
29
|
-
// If the tag name changes, we must replace the root element entirely.
|
|
30
|
-
if (this.root.tagName.toLowerCase() !== state.tagName.toLowerCase()) {
|
|
32
|
+
// 1. Tag Replacement Logic
|
|
33
|
+
if (this.root && this.root.tagName.toLowerCase() !== state.tagName.toLowerCase()) {
|
|
31
34
|
const newRoot = document.createElement(state.tagName);
|
|
32
35
|
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
// Transfer attributes/classes/listeners logic is complex
|
|
37
|
+
// We rely on re-binding events and re-applying attributes for the new root.
|
|
38
|
+
|
|
39
|
+
if (this.root.parentNode) {
|
|
40
|
+
this.root.parentNode.replaceChild(newRoot, this.root);
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
this.root.replaceWith(newRoot);
|
|
39
43
|
this.root = newRoot;
|
|
40
|
-
|
|
41
|
-
// Re-bind (BaseSkin doesn't automatically rebind on replace, so basic events might drop
|
|
42
|
-
// if they were attached locally, but this is an edge case for "readonly" building blocks).
|
|
44
|
+
this.bindEvents(this.root); // Re-bind events to new root
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
if (!this.root) return;
|
|
48
|
+
|
|
49
|
+
// 2. Base Attributes
|
|
46
50
|
this.applySkinAttributes(this.root, state);
|
|
47
51
|
|
|
48
|
-
// 3. Content
|
|
49
|
-
// We only touch the DOM if content differs to avoid stomping on interactive children
|
|
52
|
+
// 3. Content
|
|
50
53
|
if (state.contentType === 'html') {
|
|
51
|
-
|
|
52
|
-
this.root.innerHTML = state.content || '';
|
|
53
|
-
}
|
|
54
|
+
this.root.innerHTML = state.content || '';
|
|
54
55
|
} else {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
this.root.textContent = state.content || '';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 4. Inline Styles
|
|
60
|
+
if (state.inlineStyle) {
|
|
61
|
+
this.root.style.cssText = state.inlineStyle;
|
|
58
62
|
}
|
|
59
63
|
}
|
|
60
64
|
}
|