juxscript 1.0.101 → 1.0.103
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/package.json +7 -2
- package/lib/componentsv2/base/BaseEngine.ts +0 -332
- package/lib/componentsv2/base/BaseSkin.ts +0 -124
- package/lib/componentsv2/base/GlobalBus.ts +0 -60
- package/lib/componentsv2/base/OptionsContract.ts +0 -139
- package/lib/componentsv2/base/State.ts +0 -62
- package/lib/componentsv2/element/component.ts +0 -54
- package/lib/componentsv2/element/engine.ts +0 -145
- package/lib/componentsv2/element/skin.ts +0 -64
- package/lib/componentsv2/grid/component.ts +0 -57
- package/lib/componentsv2/grid/engine.ts +0 -187
- package/lib/componentsv2/grid/skin.ts +0 -106
- package/lib/componentsv2/input/component.ts +0 -28
- package/lib/componentsv2/input/engine.ts +0 -179
- package/lib/componentsv2/input/skin.ts +0 -89
- package/lib/componentsv2/list/component.ts +0 -119
- package/lib/componentsv2/list/engine.ts +0 -412
- package/lib/componentsv2/list/skin.ts +0 -369
- package/lib/componentsv2/plugins/ClientSQLitePlugin.ts +0 -154
- package/lib/componentsv2/plugins/IndexedDBPlugin.ts +0 -96
- package/lib/componentsv2/plugins/LocalStoragePlugin.ts +0 -86
- package/lib/componentsv2/plugins/ServerSQLitePlugin.ts +0 -99
- package/lib/utils/fetch.ts +0 -553
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* REGINALD'S LEDGER
|
|
3
|
-
* A protected reactive container for atomic state management.
|
|
4
|
-
*/
|
|
5
|
-
export class State<T> {
|
|
6
|
-
#value: T;
|
|
7
|
-
#subscribers: Set<(value: T) => void> = new Set();
|
|
8
|
-
#history: string[] = [];
|
|
9
|
-
#future: string[] = [];
|
|
10
|
-
|
|
11
|
-
constructor(initialValue: T) {
|
|
12
|
-
this.#value = initialValue;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
get value(): T {
|
|
16
|
-
return JSON.parse(JSON.stringify(this.#value));
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Access the timeline of past states (The Ledger).
|
|
21
|
-
*/
|
|
22
|
-
get history(): string[] {
|
|
23
|
-
return [...this.#history];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
set(newValue: T): void {
|
|
27
|
-
// Snap state serially before change
|
|
28
|
-
this.#history.push(JSON.stringify(this.#value));
|
|
29
|
-
this.#future = []; // Clear future timeline on new divergence
|
|
30
|
-
this.#value = newValue;
|
|
31
|
-
this.#notify();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
rollback(): void {
|
|
35
|
-
const previous = this.#history.pop();
|
|
36
|
-
if (previous !== undefined) {
|
|
37
|
-
this.#future.push(JSON.stringify(this.#value)); // Save current for redo
|
|
38
|
-
this.#value = JSON.parse(previous);
|
|
39
|
-
this.#notify();
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
rollforward(): void {
|
|
44
|
-
const next = this.#future.pop();
|
|
45
|
-
if (next !== undefined) {
|
|
46
|
-
this.#history.push(JSON.stringify(this.#value)); // Save current for undo
|
|
47
|
-
this.#value = JSON.parse(next);
|
|
48
|
-
this.#notify();
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
subscribe(callback: (value: T) => void): () => void {
|
|
53
|
-
this.#subscribers.add(callback);
|
|
54
|
-
callback(this.value); // Immediate handshake
|
|
55
|
-
return () => this.#subscribers.delete(callback);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
#notify(): void {
|
|
59
|
-
const currentValue = this.value;
|
|
60
|
-
this.#subscribers.forEach(callback => callback(currentValue));
|
|
61
|
-
}
|
|
62
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { ElementEngine, ElementOptions } from './engine.js';
|
|
2
|
-
import { ElementSkin } from './skin.js';
|
|
3
|
-
|
|
4
|
-
export type ElementComponent = ElementEngine & {
|
|
5
|
-
render: (targetId: string | HTMLElement) => ElementComponent;
|
|
6
|
-
injectCSS: (id: string, cssContent: string) => void;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* OPTIONS CONTRACT for Element:
|
|
11
|
-
*
|
|
12
|
-
* | Option | Type | Default | Aliases |
|
|
13
|
-
* |--------------|--------|---------|----------------------------------|
|
|
14
|
-
* | tagName | string | 'div' | tag, element, type |
|
|
15
|
-
* | content | string | '' | text, html, innerHTML, value |
|
|
16
|
-
* | contentType | string | 'text' | type, mode |
|
|
17
|
-
* | inlineStyle | string | '' | style, css, styles |
|
|
18
|
-
*
|
|
19
|
-
* METHODS (match state properties):
|
|
20
|
-
* - .tagName(value) - Set HTML tag
|
|
21
|
-
* - .content(value, type) - Set content with type
|
|
22
|
-
* - .contentType(type) - Set content type
|
|
23
|
-
* - .inlineStyle(css) - Set inline styles
|
|
24
|
-
*
|
|
25
|
-
* CONVENIENCE ALIASES:
|
|
26
|
-
* - .tag(value) → .tagName(value)
|
|
27
|
-
* - .text(value) → .content(value, 'text')
|
|
28
|
-
* - .html(value) → .content(value, 'html')
|
|
29
|
-
* - .style(css) → .inlineStyle(css)
|
|
30
|
-
*/
|
|
31
|
-
export function Element(id: string, options: ElementOptions = {}): ElementComponent {
|
|
32
|
-
const engine = new ElementEngine(id, options);
|
|
33
|
-
|
|
34
|
-
if (typeof window !== 'undefined') {
|
|
35
|
-
// @ts-ignore
|
|
36
|
-
window.juxEngines = window.juxEngines || {};
|
|
37
|
-
// @ts-ignore
|
|
38
|
-
window.juxEngines[id] = engine;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const skin = new ElementSkin(engine);
|
|
42
|
-
|
|
43
|
-
// @ts-ignore
|
|
44
|
-
engine.render = (targetId: string | HTMLElement) => {
|
|
45
|
-
const target = typeof targetId === 'string' ? document.getElementById(targetId) : targetId;
|
|
46
|
-
if (target) skin.renderSkin(target);
|
|
47
|
-
return engine as ElementComponent;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
// @ts-ignore
|
|
51
|
-
engine.injectCSS = (id: string, css: string) => skin.injectCSS(id, css);
|
|
52
|
-
|
|
53
|
-
return engine as ElementComponent;
|
|
54
|
-
}
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { BaseEngine, BaseState } from '../base/BaseEngine.js';
|
|
2
|
-
import { OptionsContractSchema } from '../base/OptionsContract.js';
|
|
3
|
-
|
|
4
|
-
export interface ElementState extends BaseState {
|
|
5
|
-
tagName: string;
|
|
6
|
-
content: string | null;
|
|
7
|
-
contentType: 'text' | 'html';
|
|
8
|
-
inlineStyle: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface ElementOptions {
|
|
12
|
-
tagName?: string;
|
|
13
|
-
content?: string;
|
|
14
|
-
contentType?: 'text' | 'html';
|
|
15
|
-
inlineStyle?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export class ElementEngine extends BaseEngine<ElementState, ElementOptions> {
|
|
19
|
-
constructor(id: string, options: ElementOptions = {}) {
|
|
20
|
-
super(id, options);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* OPTIONS CONTRACT
|
|
25
|
-
* Defines valid options with types, defaults, and aliases for common mistakes.
|
|
26
|
-
*/
|
|
27
|
-
protected get optionsSchema(): OptionsContractSchema {
|
|
28
|
-
return {
|
|
29
|
-
tagName: {
|
|
30
|
-
type: 'string',
|
|
31
|
-
default: 'div',
|
|
32
|
-
description: 'HTML tag name (e.g., "div", "span", "h1")',
|
|
33
|
-
aliases: ['tag', 'element', 'type']
|
|
34
|
-
},
|
|
35
|
-
content: {
|
|
36
|
-
type: 'string',
|
|
37
|
-
default: '',
|
|
38
|
-
description: 'Text or HTML content',
|
|
39
|
-
aliases: ['text', 'html', 'innerHTML', 'textContent', 'value']
|
|
40
|
-
},
|
|
41
|
-
contentType: {
|
|
42
|
-
type: 'string',
|
|
43
|
-
default: 'text',
|
|
44
|
-
description: 'Content type: "text" (escaped) or "html" (raw)',
|
|
45
|
-
aliases: ['type', 'mode']
|
|
46
|
-
},
|
|
47
|
-
inlineStyle: {
|
|
48
|
-
type: 'string',
|
|
49
|
-
default: '',
|
|
50
|
-
description: 'Inline CSS styles (e.g., "color: red;")',
|
|
51
|
-
aliases: ['style', 'css', 'styles']
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
protected prepareState(id: string, options: ElementOptions): ElementState {
|
|
57
|
-
return {
|
|
58
|
-
id,
|
|
59
|
-
classes: ['jux-element'],
|
|
60
|
-
visible: true,
|
|
61
|
-
disabled: false,
|
|
62
|
-
loading: false,
|
|
63
|
-
attributes: {},
|
|
64
|
-
tagName: options.tagName || 'div',
|
|
65
|
-
content: options.content || '',
|
|
66
|
-
contentType: options.contentType || 'text',
|
|
67
|
-
inlineStyle: options.inlineStyle || ''
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Set the HTML tag name (e.g., 'div', 'span', 'h1', 'section')
|
|
73
|
-
* Method name matches state property: tagName
|
|
74
|
-
*/
|
|
75
|
-
tagName(value: string): this {
|
|
76
|
-
this.updateState({ tagName: value });
|
|
77
|
-
this.emit('config', { tagName: value });
|
|
78
|
-
return this;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Set content with explicit type
|
|
83
|
-
* Method name matches state property: content
|
|
84
|
-
*/
|
|
85
|
-
content(value: string, type: 'text' | 'html' = 'text'): this {
|
|
86
|
-
this.updateState({ content: value, contentType: type });
|
|
87
|
-
return this;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Set content type
|
|
92
|
-
* Method name matches state property: contentType
|
|
93
|
-
*/
|
|
94
|
-
contentType(type: 'text' | 'html'): this {
|
|
95
|
-
this.updateState({ contentType: type });
|
|
96
|
-
return this;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Set inline styles (e.g., "color: red; margin: 10px;")
|
|
101
|
-
* Method name matches state property: inlineStyle
|
|
102
|
-
*/
|
|
103
|
-
inlineStyle(cssText: string): this {
|
|
104
|
-
this.updateState({ inlineStyle: cssText });
|
|
105
|
-
return this;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// --- Convenience Aliases (documented as such) ---
|
|
109
|
-
|
|
110
|
-
/** @alias for tagName() */
|
|
111
|
-
tag(value: string): this {
|
|
112
|
-
return this.tagName(value);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/** @alias for content(value, 'text') */
|
|
116
|
-
text(value: string): this {
|
|
117
|
-
return this.content(value, 'text');
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** @alias for content(value, 'html') */
|
|
121
|
-
html(value: string): this {
|
|
122
|
-
return this.content(value, 'html');
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/** @alias for inlineStyle() */
|
|
126
|
-
style(cssText: string): this {
|
|
127
|
-
return this.inlineStyle(cssText);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Append raw HTML to existing content
|
|
132
|
-
*/
|
|
133
|
-
appendHtml(value: string): this {
|
|
134
|
-
const current = this.state.content || '';
|
|
135
|
-
this.updateState({ content: current + value, contentType: 'html' });
|
|
136
|
-
return this;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Receives RAW DOM events from the Skin and emits them to listeners.
|
|
141
|
-
*/
|
|
142
|
-
handleEvent(eventName: string, content: any): void {
|
|
143
|
-
this.emit(eventName, content);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { BaseSkin } from '../base/BaseSkin.js';
|
|
2
|
-
import { ElementEngine, ElementState } from './engine.js';
|
|
3
|
-
import structureCss from './structure.css';
|
|
4
|
-
|
|
5
|
-
export class ElementSkin extends BaseSkin<ElementState, ElementEngine> {
|
|
6
|
-
constructor(engine: ElementEngine) {
|
|
7
|
-
super(engine);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
protected get structureCss(): string {
|
|
11
|
-
return structureCss;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
protected createRoot(): HTMLElement {
|
|
15
|
-
// Use state.tagName (consistent naming)
|
|
16
|
-
const tagName = this.engine.state.tagName || 'div';
|
|
17
|
-
return document.createElement(tagName);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
protected bindEvents(root: HTMLElement): void {
|
|
21
|
-
// ✅ Forward common events to the engine via public handler
|
|
22
|
-
// This allows engine.on('click', ...) to work without skin accessing protected 'emit'
|
|
23
|
-
const events = ['click', 'mouseenter', 'mouseleave', 'input', 'change', 'focus', 'blur'];
|
|
24
|
-
|
|
25
|
-
events.forEach(eventName => {
|
|
26
|
-
root.addEventListener(eventName, (e) => {
|
|
27
|
-
this.engine.handleEvent(eventName, e);
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
protected updateSkin(state: ElementState): void {
|
|
33
|
-
// 1. Tag Replacement Logic (uses state.tagName)
|
|
34
|
-
if (this.root && this.root.tagName.toLowerCase() !== state.tagName.toLowerCase()) {
|
|
35
|
-
const newRoot = document.createElement(state.tagName);
|
|
36
|
-
|
|
37
|
-
if (this.root.parentNode) {
|
|
38
|
-
this.root.parentNode.replaceChild(newRoot, this.root);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
this.root = newRoot;
|
|
42
|
-
this.bindEvents(this.root);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (!this.root) return;
|
|
46
|
-
|
|
47
|
-
this.root.id = state.id;
|
|
48
|
-
|
|
49
|
-
// 2. Base Attributes
|
|
50
|
-
this.applySkinAttributes(this.root, state);
|
|
51
|
-
|
|
52
|
-
// 3. Content (uses state.content and state.contentType)
|
|
53
|
-
if (state.contentType === 'html') {
|
|
54
|
-
this.root.innerHTML = state.content || '';
|
|
55
|
-
} else {
|
|
56
|
-
this.root.textContent = state.content || '';
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// 4. Inline Styles (uses state.inlineStyle)
|
|
60
|
-
if (state.inlineStyle) {
|
|
61
|
-
this.root.style.cssText = state.inlineStyle;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { GridEngine, GridOptions, GridInput } from './engine.js';
|
|
2
|
-
import { GridSkin } from './skin.js';
|
|
3
|
-
|
|
4
|
-
export type GridComponent = GridEngine & {
|
|
5
|
-
render: (targetId: string | HTMLElement) => GridComponent;
|
|
6
|
-
injectCSS: (id: string, cssContent: string) => void;
|
|
7
|
-
|
|
8
|
-
// Structure
|
|
9
|
-
rows: (config: GridInput) => GridComponent;
|
|
10
|
-
columns: (config: GridInput) => GridComponent;
|
|
11
|
-
gap: (gap: string) => GridComponent;
|
|
12
|
-
boundaryWidth: (width: string) => GridComponent;
|
|
13
|
-
boundaryHeight: (height: string) => GridComponent;
|
|
14
|
-
|
|
15
|
-
// Tools
|
|
16
|
-
gridder: (active?: boolean) => GridComponent;
|
|
17
|
-
|
|
18
|
-
// Helpers
|
|
19
|
-
getCellId: (row: number, col: number) => string;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export function Grid(id: string, options: GridOptions = {}): GridComponent {
|
|
23
|
-
const engine = new GridEngine(id, options);
|
|
24
|
-
|
|
25
|
-
if (typeof window !== 'undefined') {
|
|
26
|
-
// @ts-ignore
|
|
27
|
-
window.juxEngines = window.juxEngines || {};
|
|
28
|
-
// @ts-ignore
|
|
29
|
-
window.juxEngines[id] = engine;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const skin = new GridSkin(engine);
|
|
33
|
-
|
|
34
|
-
// Dynamic Augmentation
|
|
35
|
-
|
|
36
|
-
// @ts-ignore
|
|
37
|
-
engine.render = (targetId: string | HTMLElement) => {
|
|
38
|
-
const target = typeof targetId === 'string' ? document.getElementById(targetId) : targetId;
|
|
39
|
-
if (target) skin.renderSkin(target);
|
|
40
|
-
return engine as GridComponent;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
// @ts-ignore
|
|
44
|
-
engine.injectCSS = (id, css) => skin.injectCSS(id, css);
|
|
45
|
-
|
|
46
|
-
// --- Structural API ---
|
|
47
|
-
|
|
48
|
-
// --- Tools ---
|
|
49
|
-
// @ts-ignore
|
|
50
|
-
engine.gridder = (active?: boolean) => { engine.toggleGridder(active); return engine; };
|
|
51
|
-
|
|
52
|
-
// --- Helpers ---
|
|
53
|
-
// @ts-ignore
|
|
54
|
-
engine.getCellId = (row: number, col: number) => `${id}-${row}-${col}`;
|
|
55
|
-
|
|
56
|
-
return engine as GridComponent;
|
|
57
|
-
}
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import { BaseEngine, BaseState } from '../base/BaseEngine.js';
|
|
2
|
-
import { OptionsContractSchema } from '../base/OptionsContract.js';
|
|
3
|
-
|
|
4
|
-
export interface GridTrackConfig {
|
|
5
|
-
size?: string; // CSS size (e.g. "20%", "1fr", "100px")
|
|
6
|
-
style?: string; // Inline styles for cells in this track
|
|
7
|
-
class?: string; // Classes for cells in this track
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
// Input can be a simple number (count) or array of configs
|
|
11
|
-
export type GridInput = number | GridTrackConfig[];
|
|
12
|
-
|
|
13
|
-
export interface GridState extends BaseState {
|
|
14
|
-
rows: GridTrackConfig[];
|
|
15
|
-
columns: GridTrackConfig[];
|
|
16
|
-
gap: string;
|
|
17
|
-
boundaryWidth: string;
|
|
18
|
-
boundaryHeight: string;
|
|
19
|
-
gridder: boolean; // Debug/Blueprinting mode
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface GridOptions {
|
|
23
|
-
rows?: GridInput;
|
|
24
|
-
columns?: GridInput;
|
|
25
|
-
gap?: string;
|
|
26
|
-
boundaryWidth?: string;
|
|
27
|
-
boundaryHeight?: string;
|
|
28
|
-
gridder?: boolean;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export class GridEngine extends BaseEngine<GridState, GridOptions> {
|
|
32
|
-
constructor(id: string, options: GridOptions = {}) {
|
|
33
|
-
super(id, options);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* OPTIONS CONTRACT
|
|
38
|
-
* Defines valid options with types, defaults, and aliases for common mistakes.
|
|
39
|
-
*/
|
|
40
|
-
protected get optionsSchema(): OptionsContractSchema {
|
|
41
|
-
return {
|
|
42
|
-
rows: {
|
|
43
|
-
type: 'array',
|
|
44
|
-
default: [{ size: '1fr' }],
|
|
45
|
-
description: 'Row definitions (number or array of GridTrackConfig)',
|
|
46
|
-
aliases: ['row', 'rowCount', 'numRows']
|
|
47
|
-
},
|
|
48
|
-
columns: {
|
|
49
|
-
type: 'array',
|
|
50
|
-
default: [{ size: '1fr' }],
|
|
51
|
-
description: 'Column definitions (number or array of GridTrackConfig)',
|
|
52
|
-
aliases: ['cols', 'column', 'colCount', 'numCols', 'numColumns']
|
|
53
|
-
},
|
|
54
|
-
gap: {
|
|
55
|
-
type: 'string',
|
|
56
|
-
default: '0',
|
|
57
|
-
description: 'Gap between grid cells (CSS value)',
|
|
58
|
-
aliases: ['gridGap', 'spacing', 'gutter']
|
|
59
|
-
},
|
|
60
|
-
boundaryWidth: {
|
|
61
|
-
type: 'string',
|
|
62
|
-
default: '100%',
|
|
63
|
-
description: 'Grid container width (CSS value)',
|
|
64
|
-
aliases: ['width', 'w']
|
|
65
|
-
},
|
|
66
|
-
boundaryHeight: {
|
|
67
|
-
type: 'string',
|
|
68
|
-
default: '100%',
|
|
69
|
-
description: 'Grid container height (CSS value)',
|
|
70
|
-
aliases: ['height', 'h']
|
|
71
|
-
},
|
|
72
|
-
gridder: {
|
|
73
|
-
type: 'boolean',
|
|
74
|
-
default: false,
|
|
75
|
-
description: 'Enable debug/blueprint mode',
|
|
76
|
-
aliases: ['debug', 'blueprint', 'showGrid']
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
protected prepareState(id: string, options: GridOptions): GridState {
|
|
82
|
-
return {
|
|
83
|
-
id,
|
|
84
|
-
classes: ['jux-grid'],
|
|
85
|
-
visible: true,
|
|
86
|
-
disabled: false,
|
|
87
|
-
loading: false,
|
|
88
|
-
attributes: {},
|
|
89
|
-
rows: this.normalizeTracks(options.rows || 1),
|
|
90
|
-
columns: this.normalizeTracks(options.columns || 1),
|
|
91
|
-
gap: options.gap || '0',
|
|
92
|
-
boundaryWidth: options.boundaryWidth || '100%',
|
|
93
|
-
boundaryHeight: options.boundaryHeight || '100%',
|
|
94
|
-
gridder: options.gridder || false
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// --- Helpers ---
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* helper: Normalizes simple numbers into full config arrays.
|
|
102
|
-
* Changed from #normalizeTracks to private normalizeTracks to avoid initialization errors
|
|
103
|
-
* when called via BaseEngine constructor -> prepareState.
|
|
104
|
-
*/
|
|
105
|
-
private normalizeTracks(input: GridInput): GridTrackConfig[] {
|
|
106
|
-
if (typeof input === 'number') {
|
|
107
|
-
// Default to '1fr' for equal distribution
|
|
108
|
-
return Array(input).fill({ size: '1fr' });
|
|
109
|
-
}
|
|
110
|
-
return input.map(t => ({
|
|
111
|
-
size: t.size || '1fr',
|
|
112
|
-
style: t.style,
|
|
113
|
-
class: t.class
|
|
114
|
-
}));
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// --- State-Aligned Methods ---
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Set rows configuration
|
|
121
|
-
* Method name matches state property: rows
|
|
122
|
-
*/
|
|
123
|
-
rows(input: GridInput): this {
|
|
124
|
-
this.updateState({ rows: this.normalizeTracks(input) });
|
|
125
|
-
this.emit('layout:rows', { rows: this.state.rows });
|
|
126
|
-
return this;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Set columns configuration
|
|
131
|
-
* Method name matches state property: columns
|
|
132
|
-
*/
|
|
133
|
-
columns(input: GridInput): this {
|
|
134
|
-
this.updateState({ columns: this.normalizeTracks(input) });
|
|
135
|
-
this.emit('layout:columns', { columns: this.state.columns });
|
|
136
|
-
return this;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Set gap between cells
|
|
141
|
-
* Method name matches state property: gap
|
|
142
|
-
*/
|
|
143
|
-
gap(value: string): this {
|
|
144
|
-
this.updateState({ gap: value });
|
|
145
|
-
this.emit('layout:gap', { gap: value });
|
|
146
|
-
return this;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Set container width
|
|
151
|
-
* Method name matches state property: boundaryWidth
|
|
152
|
-
*/
|
|
153
|
-
boundaryWidth(width: string): this {
|
|
154
|
-
this.updateState({ boundaryWidth: width });
|
|
155
|
-
this.emit('layout:resize', { width });
|
|
156
|
-
return this;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Set container height
|
|
161
|
-
* Method name matches state property: boundaryHeight
|
|
162
|
-
*/
|
|
163
|
-
boundaryHeight(height: string): this {
|
|
164
|
-
this.updateState({ boundaryHeight: height });
|
|
165
|
-
this.emit('layout:resize', { height });
|
|
166
|
-
return this;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Set gridder (debug) mode
|
|
171
|
-
* Method name matches state property: gridder
|
|
172
|
-
*/
|
|
173
|
-
gridder(active: boolean): this {
|
|
174
|
-
this.updateState({ gridder: active });
|
|
175
|
-
this.emit('debug:gridder', { active });
|
|
176
|
-
return this;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Toggle gridder mode
|
|
181
|
-
* @alias Convenience method that toggles gridder state
|
|
182
|
-
*/
|
|
183
|
-
toggleGridder(active?: boolean): this {
|
|
184
|
-
const next = active !== undefined ? active : !this.state.gridder;
|
|
185
|
-
return this.gridder(next);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { BaseSkin } from '../base/BaseSkin.js';
|
|
2
|
-
import { GridEngine, GridState } from './engine.js';
|
|
3
|
-
import structureCss from './structure.css';
|
|
4
|
-
|
|
5
|
-
export class GridSkin extends BaseSkin<GridState, GridEngine> {
|
|
6
|
-
|
|
7
|
-
#cells = new Map<string, HTMLElement>();
|
|
8
|
-
|
|
9
|
-
constructor(engine: GridEngine) {
|
|
10
|
-
super(engine);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
protected get structureCss(): string {
|
|
15
|
-
return structureCss;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
protected bindEvents(root: HTMLElement): void {
|
|
19
|
-
root.addEventListener('click', (e) => {
|
|
20
|
-
if (this.engine.state.gridder) {
|
|
21
|
-
const cell = (e.target as HTMLElement).closest('.jux-grid-cell');
|
|
22
|
-
if (cell && cell.id) {
|
|
23
|
-
console.log(`[Ordinal Grid] Targeted Cell: #${cell.id}`);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
protected updateSkin(state: GridState): void {
|
|
30
|
-
if (!this.root) return;
|
|
31
|
-
this.applySkinAttributes(this.root, state);
|
|
32
|
-
|
|
33
|
-
this.root.className = state.classes.join(' ');
|
|
34
|
-
|
|
35
|
-
// 1. Container Styles
|
|
36
|
-
this.root.style.width = state.boundaryWidth;
|
|
37
|
-
this.root.style.height = state.boundaryHeight;
|
|
38
|
-
this.root.style.gap = state.gap;
|
|
39
|
-
|
|
40
|
-
// CSS Grid Definitions
|
|
41
|
-
const rowSizes = state.rows.map(r => r.size).join(' ');
|
|
42
|
-
const colSizes = state.columns.map(c => c.size).join(' ');
|
|
43
|
-
|
|
44
|
-
this.root.style.gridTemplateRows = rowSizes;
|
|
45
|
-
this.root.style.gridTemplateColumns = colSizes;
|
|
46
|
-
|
|
47
|
-
// Toggle Debug Class
|
|
48
|
-
if (state.gridder) {
|
|
49
|
-
this.root.classList.add('jux-gridder-active');
|
|
50
|
-
} else {
|
|
51
|
-
this.root.classList.remove('jux-gridder-active');
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// 2. Structural Reconciliation
|
|
55
|
-
const validIds = new Set<string>();
|
|
56
|
-
|
|
57
|
-
state.rows.forEach((rowConfig, rIndex) => {
|
|
58
|
-
state.columns.forEach((colConfig, cIndex) => {
|
|
59
|
-
const cellId = `${state.id}-${rIndex}-${cIndex}`;
|
|
60
|
-
validIds.add(cellId);
|
|
61
|
-
|
|
62
|
-
let cell = this.#cells.get(cellId);
|
|
63
|
-
|
|
64
|
-
if (!cell) {
|
|
65
|
-
cell = document.createElement('div');
|
|
66
|
-
cell.id = cellId;
|
|
67
|
-
cell.className = 'jux-grid-cell';
|
|
68
|
-
cell.dataset.row = String(rIndex);
|
|
69
|
-
cell.dataset.col = String(cIndex);
|
|
70
|
-
|
|
71
|
-
const label = document.createElement('span');
|
|
72
|
-
label.className = 'jux-grid-ordinal-label';
|
|
73
|
-
label.textContent = `#${rIndex}-${cIndex}`;
|
|
74
|
-
cell.appendChild(label);
|
|
75
|
-
|
|
76
|
-
this.#cells.set(cellId, cell);
|
|
77
|
-
|
|
78
|
-
if (!this.root!.contains(cell)) {
|
|
79
|
-
this.root!.appendChild(cell);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
cell.style.cssText = '';
|
|
84
|
-
cell.className = 'jux-grid-cell';
|
|
85
|
-
|
|
86
|
-
if (rowConfig.style) cell.style.cssText += rowConfig.style;
|
|
87
|
-
if (colConfig.style) cell.style.cssText += colConfig.style;
|
|
88
|
-
|
|
89
|
-
if (rowConfig.class) cell.classList.add(rowConfig.class);
|
|
90
|
-
if (colConfig.class) cell.classList.add(colConfig.class);
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// 3. Cleanup Orphans
|
|
95
|
-
this.#cells.forEach((cell, id) => {
|
|
96
|
-
if (!validIds.has(id)) {
|
|
97
|
-
cell.remove();
|
|
98
|
-
this.#cells.delete(id);
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
protected createRoot(): HTMLElement {
|
|
104
|
-
return document.createElement('div');
|
|
105
|
-
}
|
|
106
|
-
}
|