lightview 2.1.0 → 2.2.2
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/build-bundles.mjs +2 -6
- package/build.js +236 -46
- package/components/data-display/avatar.js +25 -1
- package/components/data-display/chart.js +22 -5
- package/components/data-display/countdown.js +3 -2
- package/components/data-input/checkbox.js +23 -1
- package/components/data-input/input.js +24 -1
- package/components/data-input/radio.js +37 -2
- package/components/data-input/select.js +24 -1
- package/components/data-input/toggle.js +21 -1
- package/components/navigation/breadcrumbs.js +42 -2
- package/docs/assets/js/examplify.js +1 -1
- package/docs/cdom-nav.html +32 -6
- package/docs/cdom.html +610 -180
- package/docs/components/avatar.html +24 -54
- package/docs/components/badge.html +14 -14
- package/docs/components/breadcrumbs.html +95 -29
- package/docs/components/chart-area.html +3 -3
- package/docs/components/chart-bar.html +4 -181
- package/docs/components/chart-column.html +4 -189
- package/docs/components/chart-line.html +3 -3
- package/docs/components/chart-pie.html +112 -166
- package/docs/components/chart.html +11 -13
- package/docs/components/checkbox.html +48 -28
- package/docs/components/collapse.html +6 -6
- package/docs/components/countdown.html +12 -12
- package/docs/components/dropdown.html +1 -1
- package/docs/components/file-input.html +4 -4
- package/docs/components/footer.html +11 -11
- package/docs/components/input.html +45 -29
- package/docs/components/join.html +4 -4
- package/docs/components/kbd.html +3 -3
- package/docs/components/loading.html +41 -53
- package/docs/components/pagination.html +4 -4
- package/docs/components/progress.html +6 -4
- package/docs/components/radio.html +42 -31
- package/docs/components/select.html +48 -59
- package/docs/components/toggle.html +44 -25
- package/docs/getting-started/index.html +4 -4
- package/jprx/LICENSE +21 -0
- package/jprx/README.md +130 -0
- package/{cdom → jprx}/helpers/array.js +9 -4
- package/{cdom → jprx}/helpers/state.js +6 -3
- package/jprx/index.js +69 -0
- package/jprx/package.json +24 -0
- package/jprx/parser.js +1517 -0
- package/lightview-all.js +3785 -1
- package/lightview-cdom.js +2128 -1
- package/lightview-router.js +179 -208
- package/lightview-x.js +1435 -1
- package/lightview.js +613 -1
- package/package.json +5 -2
- package/src/lightview-cdom.js +201 -49
- package/src/lightview-router.js +210 -0
- package/src/lightview-x.js +104 -55
- package/src/lightview.js +12 -1
- package/{watch.js → start-dev.js} +2 -1
- package/tests/cdom/parser.test.js +83 -12
- package/wrangler.toml +0 -3
- package/cdom/parser.js +0 -602
- package/test-text-tag.js +0 -6
- /package/{cdom → jprx}/helpers/compare.js +0 -0
- /package/{cdom → jprx}/helpers/conditional.js +0 -0
- /package/{cdom → jprx}/helpers/datetime.js +0 -0
- /package/{cdom → jprx}/helpers/format.js +0 -0
- /package/{cdom → jprx}/helpers/logic.js +0 -0
- /package/{cdom → jprx}/helpers/lookup.js +0 -0
- /package/{cdom → jprx}/helpers/math.js +0 -0
- /package/{cdom → jprx}/helpers/network.js +0 -0
- /package/{cdom → jprx}/helpers/stats.js +0 -0
- /package/{cdom → jprx}/helpers/string.js +0 -0
package/src/lightview-x.js
CHANGED
|
@@ -92,7 +92,7 @@ const convertObjectDOM = (obj) => {
|
|
|
92
92
|
// ============= COMPONENT CONFIGURATION =============
|
|
93
93
|
// Global configuration for Lightview components
|
|
94
94
|
|
|
95
|
-
const DAISYUI_CDN = 'https://cdn.jsdelivr.net/npm/daisyui@
|
|
95
|
+
const DAISYUI_CDN = 'https://cdn.jsdelivr.net/npm/daisyui@4.12.23/dist/full.min.css';
|
|
96
96
|
|
|
97
97
|
// Component configuration (set by initComponents)
|
|
98
98
|
const componentConfig = {
|
|
@@ -101,7 +101,8 @@ const componentConfig = {
|
|
|
101
101
|
daisyStyleSheet: null,
|
|
102
102
|
themeStyleSheet: null, // Global theme stylesheet
|
|
103
103
|
componentStyleSheets: new Map(),
|
|
104
|
-
customStyleSheets: new Map() // Registry for named custom stylesheets
|
|
104
|
+
customStyleSheets: new Map(), // Registry for named custom stylesheets
|
|
105
|
+
customStyleSheetPromises: new Map() // Cache for pending stylesheet fetches
|
|
105
106
|
};
|
|
106
107
|
|
|
107
108
|
/**
|
|
@@ -111,36 +112,45 @@ const componentConfig = {
|
|
|
111
112
|
* @returns {Promise<void>}
|
|
112
113
|
*/
|
|
113
114
|
const registerStyleSheet = async (nameOrIdOrUrl, cssText) => {
|
|
114
|
-
if (componentConfig.customStyleSheets.has(nameOrIdOrUrl)) return;
|
|
115
|
+
if (componentConfig.customStyleSheets.has(nameOrIdOrUrl)) return componentConfig.customStyleSheets.get(nameOrIdOrUrl);
|
|
116
|
+
if (componentConfig.customStyleSheetPromises.has(nameOrIdOrUrl)) return componentConfig.customStyleSheetPromises.get(nameOrIdOrUrl);
|
|
115
117
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
118
|
+
const promise = (async () => {
|
|
119
|
+
try {
|
|
120
|
+
let finalCss = cssText;
|
|
121
|
+
|
|
122
|
+
if (finalCss === undefined) {
|
|
123
|
+
if (nameOrIdOrUrl.startsWith('#')) {
|
|
124
|
+
// ID selector - search synchronously
|
|
125
|
+
const el = document.querySelector(nameOrIdOrUrl);
|
|
126
|
+
if (el) {
|
|
127
|
+
finalCss = el.textContent;
|
|
128
|
+
} else {
|
|
129
|
+
throw new Error(`Style block '${nameOrIdOrUrl}' not found`);
|
|
130
|
+
}
|
|
125
131
|
} else {
|
|
126
|
-
|
|
132
|
+
// Assume URL
|
|
133
|
+
const response = await fetch(nameOrIdOrUrl);
|
|
134
|
+
if (!response.ok) throw new Error(`Fetch failed: ${response.status}`);
|
|
135
|
+
finalCss = await response.text();
|
|
127
136
|
}
|
|
128
|
-
} else {
|
|
129
|
-
// Assume URL
|
|
130
|
-
const response = await fetch(nameOrIdOrUrl);
|
|
131
|
-
if (!response.ok) throw new Error(`Fetch failed: ${response.status}`);
|
|
132
|
-
finalCss = await response.text();
|
|
133
137
|
}
|
|
134
|
-
}
|
|
135
138
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
if (finalCss !== undefined) {
|
|
140
|
+
const sheet = new CSSStyleSheet();
|
|
141
|
+
sheet.replaceSync(finalCss);
|
|
142
|
+
componentConfig.customStyleSheets.set(nameOrIdOrUrl, sheet);
|
|
143
|
+
return sheet;
|
|
144
|
+
}
|
|
145
|
+
} catch (e) {
|
|
146
|
+
console.error(`LightviewX: Failed to register stylesheet '${nameOrIdOrUrl}':`, e);
|
|
147
|
+
} finally {
|
|
148
|
+
componentConfig.customStyleSheetPromises.delete(nameOrIdOrUrl);
|
|
140
149
|
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
150
|
+
})();
|
|
151
|
+
|
|
152
|
+
componentConfig.customStyleSheetPromises.set(nameOrIdOrUrl, promise);
|
|
153
|
+
return promise;
|
|
144
154
|
};
|
|
145
155
|
|
|
146
156
|
// Theme Signal
|
|
@@ -1043,7 +1053,11 @@ const activateReactiveSyntax = (root, LV) => {
|
|
|
1043
1053
|
try {
|
|
1044
1054
|
const val = fn(LV.state, LV.signal);
|
|
1045
1055
|
if (isAttr) {
|
|
1046
|
-
|
|
1056
|
+
if (attrName.startsWith('cdom-')) {
|
|
1057
|
+
node[attrName] = val;
|
|
1058
|
+
} else {
|
|
1059
|
+
(val === null || val === undefined || val === false) ? node.removeAttribute(attrName) : node.setAttribute(attrName, val);
|
|
1060
|
+
}
|
|
1047
1061
|
} else node.textContent = val !== undefined ? val : '';
|
|
1048
1062
|
} catch (e) { /* Effect execution failed */ }
|
|
1049
1063
|
});
|
|
@@ -1399,11 +1413,28 @@ const customElementWrapper = (Component, config = {}) => {
|
|
|
1399
1413
|
}
|
|
1400
1414
|
|
|
1401
1415
|
connectedCallback() {
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1416
|
+
let adopted = false;
|
|
1417
|
+
// Attempt to use pre-parsed adopted stylesheets for performance
|
|
1418
|
+
if (componentConfig.daisyStyleSheet) {
|
|
1419
|
+
try {
|
|
1420
|
+
const sheets = [componentConfig.daisyStyleSheet];
|
|
1421
|
+
if (componentConfig.themeStyleSheet) {
|
|
1422
|
+
sheets.push(componentConfig.themeStyleSheet);
|
|
1423
|
+
}
|
|
1424
|
+
this.shadowRoot.adoptedStyleSheets = sheets;
|
|
1425
|
+
adopted = true;
|
|
1426
|
+
} catch (e) {
|
|
1427
|
+
// Browser might not support adoptedStyleSheets
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
// Fallback to link tag if adoption failed or sheet wasn't loaded yet
|
|
1432
|
+
if (!adopted) {
|
|
1433
|
+
const link = document.createElement('link');
|
|
1434
|
+
link.rel = 'stylesheet';
|
|
1435
|
+
link.href = DAISYUI_CDN;
|
|
1436
|
+
this.shadowRoot.appendChild(link);
|
|
1437
|
+
}
|
|
1407
1438
|
|
|
1408
1439
|
// Sync theme from document
|
|
1409
1440
|
const themeWrapper = document.createElement('div');
|
|
@@ -1458,22 +1489,30 @@ const customElementWrapper = (Component, config = {}) => {
|
|
|
1458
1489
|
|
|
1459
1490
|
if (!componentInfo) return null;
|
|
1460
1491
|
|
|
1461
|
-
const { component, attributeMap = {}
|
|
1492
|
+
const { component, attributeMap = {} } = componentInfo;
|
|
1462
1493
|
const attributes = {};
|
|
1463
1494
|
|
|
1464
|
-
// Parse attributes
|
|
1465
|
-
|
|
1466
|
-
const
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1495
|
+
// Parse all attributes
|
|
1496
|
+
for (const attr of child.attributes) {
|
|
1497
|
+
const name = attr.name.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
1498
|
+
const type = attributeMap[name];
|
|
1499
|
+
const value = attr.value;
|
|
1500
|
+
|
|
1501
|
+
if (type === Boolean) {
|
|
1502
|
+
attributes[name] = value === 'true' || value === '';
|
|
1503
|
+
} else if (type === Number) {
|
|
1504
|
+
attributes[name] = Number(value);
|
|
1505
|
+
} else if (type === Array || type === Object) {
|
|
1506
|
+
try {
|
|
1507
|
+
attributes[name] = JSON.parse(value);
|
|
1508
|
+
} catch (e) {
|
|
1509
|
+
console.warn(`[Lightview] Failed to parse child attribute ${name} as JSON:`, value);
|
|
1510
|
+
attributes[name] = value;
|
|
1474
1511
|
}
|
|
1512
|
+
} else {
|
|
1513
|
+
attributes[name] = value;
|
|
1475
1514
|
}
|
|
1476
|
-
}
|
|
1515
|
+
}
|
|
1477
1516
|
|
|
1478
1517
|
// Copy event handlers
|
|
1479
1518
|
if (child.onclick) attributes.onclick = child.onclick.bind(child);
|
|
@@ -1481,7 +1520,7 @@ const customElementWrapper = (Component, config = {}) => {
|
|
|
1481
1520
|
return {
|
|
1482
1521
|
tag: component,
|
|
1483
1522
|
attributes,
|
|
1484
|
-
children:
|
|
1523
|
+
children: Array.from(child.childNodes)
|
|
1485
1524
|
};
|
|
1486
1525
|
}).filter(Boolean);
|
|
1487
1526
|
}
|
|
@@ -1489,18 +1528,28 @@ const customElementWrapper = (Component, config = {}) => {
|
|
|
1489
1528
|
render() {
|
|
1490
1529
|
// Build props from attributes
|
|
1491
1530
|
const props = { useShadow: false }; // Wrapper already created shadow DOM
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1531
|
+
|
|
1532
|
+
// Collect all attributes
|
|
1533
|
+
for (const attr of this.attributes) {
|
|
1534
|
+
const name = attr.name.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
1535
|
+
const type = attributeMap[name];
|
|
1536
|
+
const value = attr.value;
|
|
1537
|
+
|
|
1538
|
+
if (type === Boolean) {
|
|
1539
|
+
props[name] = value === 'true' || value === '';
|
|
1540
|
+
} else if (type === Number) {
|
|
1541
|
+
props[name] = Number(value);
|
|
1542
|
+
} else if (type === Array || type === Object) {
|
|
1543
|
+
try {
|
|
1544
|
+
props[name] = JSON.parse(value);
|
|
1545
|
+
} catch (e) {
|
|
1546
|
+
console.warn(`[Lightview] Failed to parse ${name} as JSON:`, value);
|
|
1547
|
+
props[name] = value;
|
|
1501
1548
|
}
|
|
1549
|
+
} else {
|
|
1550
|
+
props[name] = value;
|
|
1502
1551
|
}
|
|
1503
|
-
}
|
|
1552
|
+
}
|
|
1504
1553
|
|
|
1505
1554
|
const vdomChildren = this.parseChildrenToVDOM();
|
|
1506
1555
|
// If no child elements are mapped, use a slot to project light DOM
|
package/src/lightview.js
CHANGED
|
@@ -270,7 +270,7 @@ const setAttributeValue = (domNode, key, value) => {
|
|
|
270
270
|
value = 'javascript:void(0)'; // Safer fallback than # which might trigger scroll or router
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
if (NODE_PROPERTIES.has(key) || isBool) {
|
|
273
|
+
if (NODE_PROPERTIES.has(key) || isBool || key.startsWith('cdom-')) {
|
|
274
274
|
domNode[key] = isBool ? (value !== null && value !== undefined && value !== false && value !== 'false') : value;
|
|
275
275
|
} else if (value === null || value === undefined) {
|
|
276
276
|
domNode.removeAttribute(key);
|
|
@@ -428,6 +428,17 @@ const processChildren = (children, targetNode, clearExisting = true) => {
|
|
|
428
428
|
// Static text
|
|
429
429
|
targetNode.appendChild(document.createTextNode(child));
|
|
430
430
|
childElements.push(child);
|
|
431
|
+
} else if (child instanceof Node) {
|
|
432
|
+
// Raw DOM node
|
|
433
|
+
const node = child.domEl || child;
|
|
434
|
+
if (node instanceof HTMLElement || node instanceof SVGElement) {
|
|
435
|
+
const wrapped = wrapDomElement(node, node.tagName.toLowerCase());
|
|
436
|
+
targetNode.appendChild(node);
|
|
437
|
+
childElements.push(wrapped);
|
|
438
|
+
} else {
|
|
439
|
+
targetNode.appendChild(node);
|
|
440
|
+
childElements.push(child);
|
|
441
|
+
}
|
|
431
442
|
} else if (child && type === 'object' && child.tag) {
|
|
432
443
|
// Child element (already wrapped or plain object) - tag can be string or function
|
|
433
444
|
const childEl = child.domEl ? child : element(child.tag, child.attributes || {}, child.children || []);
|
|
@@ -14,7 +14,8 @@ const allowedDirs = ['docs', 'components', 'middleware'];
|
|
|
14
14
|
function build() {
|
|
15
15
|
try {
|
|
16
16
|
console.log('Running full build...');
|
|
17
|
-
execSync('node build.
|
|
17
|
+
execSync('node build-bundles.mjs',, { stdio: 'inherit' });
|
|
18
|
+
execSync('node build.js --env=dev', { stdio: 'inherit' });
|
|
18
19
|
} catch (e) {
|
|
19
20
|
console.error('Build failed:', e);
|
|
20
21
|
}
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
2
|
import Lightview from '../../src/lightview.js';
|
|
3
3
|
import LightviewX from '../../src/lightview-x.js';
|
|
4
|
-
import { resolvePath, parseExpression, registerHelper } from '../../
|
|
5
|
-
import { registerMathHelpers } from '../../
|
|
6
|
-
import { registerLogicHelpers } from '../../
|
|
7
|
-
import { registerStringHelpers } from '../../
|
|
8
|
-
import { registerArrayHelpers } from '../../
|
|
9
|
-
import { registerCompareHelpers } from '../../
|
|
10
|
-
import { registerConditionalHelpers } from '../../
|
|
11
|
-
import { registerDateTimeHelpers } from '../../
|
|
12
|
-
import { registerFormatHelpers } from '../../
|
|
13
|
-
import { registerLookupHelpers } from '../../
|
|
14
|
-
import { registerStatsHelpers } from '../../
|
|
15
|
-
import { registerStateHelpers } from '../../
|
|
4
|
+
import { resolvePath, parseExpression, registerHelper, parseCDOMC } from '../../jprx/parser.js';
|
|
5
|
+
import { registerMathHelpers } from '../../jprx/helpers/math.js';
|
|
6
|
+
import { registerLogicHelpers } from '../../jprx/helpers/logic.js';
|
|
7
|
+
import { registerStringHelpers } from '../../jprx/helpers/string.js';
|
|
8
|
+
import { registerArrayHelpers } from '../../jprx/helpers/array.js';
|
|
9
|
+
import { registerCompareHelpers } from '../../jprx/helpers/compare.js';
|
|
10
|
+
import { registerConditionalHelpers } from '../../jprx/helpers/conditional.js';
|
|
11
|
+
import { registerDateTimeHelpers } from '../../jprx/helpers/datetime.js';
|
|
12
|
+
import { registerFormatHelpers } from '../../jprx/helpers/format.js';
|
|
13
|
+
import { registerLookupHelpers } from '../../jprx/helpers/lookup.js';
|
|
14
|
+
import { registerStatsHelpers } from '../../jprx/helpers/stats.js';
|
|
15
|
+
import { registerStateHelpers } from '../../jprx/helpers/state.js';
|
|
16
|
+
import { hydrate } from '../../src/lightview-cdom.js';
|
|
16
17
|
|
|
17
18
|
describe('cdom Parser', () => {
|
|
18
19
|
beforeEach(() => {
|
|
@@ -105,4 +106,74 @@ describe('cdom Parser', () => {
|
|
|
105
106
|
expect(expr.value).toBe('CHARLIE');
|
|
106
107
|
});
|
|
107
108
|
});
|
|
109
|
+
|
|
110
|
+
describe('CDOMC Parser', () => {
|
|
111
|
+
it('parses unquoted $ expressions as strings', () => {
|
|
112
|
+
const input = '{ button: { onclick: $increment($/count), children: "Click" } }';
|
|
113
|
+
const result = parseCDOMC(input);
|
|
114
|
+
|
|
115
|
+
expect(result).toEqual({
|
|
116
|
+
button: {
|
|
117
|
+
onclick: '$increment($/count)',
|
|
118
|
+
children: 'Click'
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('parses unquoted cdom-state with object value', () => {
|
|
124
|
+
const input = '{ div: { cdom-state: { count: 0 }, children: [] } }';
|
|
125
|
+
const result = parseCDOMC(input);
|
|
126
|
+
|
|
127
|
+
expect(result).toEqual({
|
|
128
|
+
div: {
|
|
129
|
+
'cdom-state': { count: 0 },
|
|
130
|
+
children: []
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('preserves $ prefix in simple paths', () => {
|
|
136
|
+
const input = '{ input: { cdom-bind: $/user/name } }';
|
|
137
|
+
const result = parseCDOMC(input);
|
|
138
|
+
|
|
139
|
+
expect(result).toEqual({
|
|
140
|
+
input: {
|
|
141
|
+
'cdom-bind': '$/user/name'
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('Hydration', () => {
|
|
148
|
+
it('converts event handler $ expressions to functions', () => {
|
|
149
|
+
|
|
150
|
+
const input = {
|
|
151
|
+
button: {
|
|
152
|
+
onclick: '$increment($/count)',
|
|
153
|
+
children: ['Click']
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const result = hydrate(input);
|
|
158
|
+
|
|
159
|
+
// onclick should now be a function
|
|
160
|
+
expect(typeof result.button.onclick).toBe('function');
|
|
161
|
+
expect(result.button.children).toEqual(['Click']);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('preserves non-$ event handlers as strings', () => {
|
|
165
|
+
|
|
166
|
+
const input = {
|
|
167
|
+
button: {
|
|
168
|
+
onclick: 'alert("hello")',
|
|
169
|
+
children: ['Click']
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const result = hydrate(input);
|
|
174
|
+
|
|
175
|
+
// onclick should remain a string (non-$ expression)
|
|
176
|
+
expect(result.button.onclick).toBe('alert("hello")');
|
|
177
|
+
});
|
|
178
|
+
});
|
|
108
179
|
});
|