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.
@@ -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
- }