@uniweb/core 0.1.10 → 0.1.11
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/README.md +0 -1
- package/package.json +2 -2
- package/src/block.js +6 -5
- package/src/page.js +76 -1
- package/src/uniweb.js +25 -1
- package/src/website.js +33 -1
package/README.md
CHANGED
|
@@ -126,7 +126,6 @@ page.label // Short navigation label (or null)
|
|
|
126
126
|
page.order // Sort order
|
|
127
127
|
page.children // Child pages (for nested hierarchy)
|
|
128
128
|
page.website // Back-reference to parent Website
|
|
129
|
-
page.site // Alias for page.website
|
|
130
129
|
|
|
131
130
|
// Navigation visibility
|
|
132
131
|
page.hidden // Hidden from all navigation
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Core classes for the Uniweb platform - Uniweb, Website, Page, Block",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -27,6 +27,6 @@
|
|
|
27
27
|
"node": ">=20.19"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@uniweb/semantic-parser": "1.0.
|
|
30
|
+
"@uniweb/semantic-parser": "1.0.8"
|
|
31
31
|
}
|
|
32
32
|
}
|
package/src/block.js
CHANGED
|
@@ -175,17 +175,18 @@ export default class Block {
|
|
|
175
175
|
return null
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
// Get component
|
|
179
|
-
|
|
180
|
-
const blockConfig = this.Component.block || this.Component.blockDefaults || {}
|
|
178
|
+
// Get runtime metadata for this component (from meta.js, extracted at build time)
|
|
179
|
+
const meta = globalThis.uniweb?.getComponentMeta(this.type) || {}
|
|
181
180
|
|
|
182
181
|
// Initialize state (dynamic, can change at runtime)
|
|
183
|
-
|
|
182
|
+
// Source: meta.js initialState field
|
|
183
|
+
const stateDefaults = meta.initialState
|
|
184
184
|
this.startState = stateDefaults ? { ...stateDefaults } : null
|
|
185
185
|
this.initState()
|
|
186
186
|
|
|
187
187
|
// Initialize context (static, per component type)
|
|
188
|
-
|
|
188
|
+
// Source: meta.js context field
|
|
189
|
+
this.context = meta.context ? { ...meta.context } : null
|
|
189
190
|
|
|
190
191
|
return this.Component
|
|
191
192
|
}
|
package/src/page.js
CHANGED
|
@@ -51,7 +51,6 @@ export default class Page {
|
|
|
51
51
|
|
|
52
52
|
// Back-reference to website
|
|
53
53
|
this.website = website
|
|
54
|
-
this.site = website // Alias
|
|
55
54
|
|
|
56
55
|
// Scroll position memory (for navigation restoration)
|
|
57
56
|
this.scrollY = 0
|
|
@@ -385,4 +384,80 @@ export default class Page {
|
|
|
385
384
|
hasChildren() {
|
|
386
385
|
return this.children.length > 0
|
|
387
386
|
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Check if page has body content (sections)
|
|
390
|
+
* @returns {boolean}
|
|
391
|
+
*/
|
|
392
|
+
hasContent() {
|
|
393
|
+
return this.pageBlocks.body.length > 0
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// ─────────────────────────────────────────────────────────────────
|
|
397
|
+
// Active Route Detection
|
|
398
|
+
// ─────────────────────────────────────────────────────────────────
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Get the first navigable route for this page.
|
|
402
|
+
* If page has no content, recursively finds first child with content.
|
|
403
|
+
* Useful for category pages that are just navigation containers.
|
|
404
|
+
*
|
|
405
|
+
* @returns {string} The route to navigate to
|
|
406
|
+
*
|
|
407
|
+
* @example
|
|
408
|
+
* // For a "Docs" category page with no content but children:
|
|
409
|
+
* // page.route = '/docs'
|
|
410
|
+
* // page.hasContent() = false
|
|
411
|
+
* // First child with content: '/docs/getting-started'
|
|
412
|
+
* page.getNavigableRoute() // Returns '/docs/getting-started'
|
|
413
|
+
*/
|
|
414
|
+
getNavigableRoute() {
|
|
415
|
+
if (this.hasContent()) return this.route
|
|
416
|
+
for (const child of this.children || []) {
|
|
417
|
+
const route = child.getNavigableRoute()
|
|
418
|
+
if (route) return route
|
|
419
|
+
}
|
|
420
|
+
return this.route // Fallback to own route
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Get route without leading/trailing slashes.
|
|
425
|
+
* Useful for route comparisons.
|
|
426
|
+
*
|
|
427
|
+
* @returns {string} Normalized route (e.g., 'docs/getting-started')
|
|
428
|
+
*/
|
|
429
|
+
getNormalizedRoute() {
|
|
430
|
+
return (this.route || '').replace(/^\//, '').replace(/\/$/, '')
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Check if this page matches the given route exactly.
|
|
435
|
+
*
|
|
436
|
+
* @param {string} route - Normalized route (no leading/trailing slashes)
|
|
437
|
+
* @returns {boolean} True if this page's route matches
|
|
438
|
+
*/
|
|
439
|
+
isActiveFor(route) {
|
|
440
|
+
return this.getNormalizedRoute() === route
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Check if this page or any descendant matches the given route.
|
|
445
|
+
* Useful for highlighting parent nav items when a child page is active.
|
|
446
|
+
*
|
|
447
|
+
* @param {string} route - Normalized route (no leading/trailing slashes)
|
|
448
|
+
* @returns {boolean} True if this page or a descendant is active
|
|
449
|
+
*
|
|
450
|
+
* @example
|
|
451
|
+
* // Page route: '/docs'
|
|
452
|
+
* // Current route: 'docs/getting-started/installation'
|
|
453
|
+
* page.isActiveOrAncestor('docs/getting-started/installation') // true
|
|
454
|
+
*/
|
|
455
|
+
isActiveOrAncestor(route) {
|
|
456
|
+
const pageRoute = this.getNormalizedRoute()
|
|
457
|
+
if (pageRoute === route) return true
|
|
458
|
+
// Check if route starts with this page's route followed by /
|
|
459
|
+
// Handle empty pageRoute (root) specially
|
|
460
|
+
if (pageRoute === '') return true // Root is ancestor of all
|
|
461
|
+
return route.startsWith(pageRoute + '/')
|
|
462
|
+
}
|
|
388
463
|
}
|
package/src/uniweb.js
CHANGED
|
@@ -14,7 +14,8 @@ export default class Uniweb {
|
|
|
14
14
|
this.childBlockRenderer = null // Function to render child blocks
|
|
15
15
|
this.routingComponents = {} // Link, SafeHtml, useNavigate, etc.
|
|
16
16
|
this.foundation = null // The loaded foundation module
|
|
17
|
-
this.foundationConfig = {} // Configuration from foundation
|
|
17
|
+
this.foundationConfig = {} // Configuration from foundation (capabilities)
|
|
18
|
+
this.meta = {} // Per-component runtime metadata (from meta.js)
|
|
18
19
|
this.language = 'en'
|
|
19
20
|
|
|
20
21
|
// Initialize analytics (disabled by default, configure via site config)
|
|
@@ -27,6 +28,29 @@ export default class Uniweb {
|
|
|
27
28
|
*/
|
|
28
29
|
setFoundation(foundation) {
|
|
29
30
|
this.foundation = foundation
|
|
31
|
+
|
|
32
|
+
// Store per-component metadata if present
|
|
33
|
+
if (foundation.meta) {
|
|
34
|
+
this.meta = foundation.meta
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get runtime metadata for a component
|
|
40
|
+
* @param {string} componentName
|
|
41
|
+
* @returns {Object|null} Meta with defaults, context, initialState, background, data
|
|
42
|
+
*/
|
|
43
|
+
getComponentMeta(componentName) {
|
|
44
|
+
return this.meta[componentName] || null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get default param values for a component
|
|
49
|
+
* @param {string} componentName
|
|
50
|
+
* @returns {Object} Default values (empty object if none)
|
|
51
|
+
*/
|
|
52
|
+
getComponentDefaults(componentName) {
|
|
53
|
+
return this.meta[componentName]?.defaults || {}
|
|
30
54
|
}
|
|
31
55
|
|
|
32
56
|
/**
|
package/src/website.js
CHANGED
|
@@ -489,11 +489,12 @@ export default class Website {
|
|
|
489
489
|
const buildPageInfo = (page) => ({
|
|
490
490
|
id: page.id,
|
|
491
491
|
route: page.getNavRoute(), // Use canonical nav route (e.g., '/' for index pages)
|
|
492
|
+
navigableRoute: page.getNavigableRoute(), // First route with content (for links)
|
|
492
493
|
title: page.title,
|
|
493
494
|
label: page.getLabel(),
|
|
494
495
|
description: page.description,
|
|
495
496
|
order: page.order,
|
|
496
|
-
hasContent: page.
|
|
497
|
+
hasContent: page.hasContent(),
|
|
497
498
|
children: nested && page.hasChildren()
|
|
498
499
|
? page.children.filter(isPageVisible).map(buildPageInfo)
|
|
499
500
|
: []
|
|
@@ -528,4 +529,35 @@ export default class Website {
|
|
|
528
529
|
getAllPages(includeHidden = false) {
|
|
529
530
|
return this.getPageHierarchy({ nested: false, includeHidden })
|
|
530
531
|
}
|
|
532
|
+
|
|
533
|
+
// ─────────────────────────────────────────────────────────────────
|
|
534
|
+
// Active Route API (for navigation components)
|
|
535
|
+
// ─────────────────────────────────────────────────────────────────
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Get the current active route, normalized (no leading/trailing slashes).
|
|
539
|
+
* Works in both SSR (from activePage) and client (from activePage).
|
|
540
|
+
*
|
|
541
|
+
* @returns {string} Normalized route (e.g., 'docs/getting-started')
|
|
542
|
+
*
|
|
543
|
+
* @example
|
|
544
|
+
* website.getActiveRoute() // 'docs/getting-started'
|
|
545
|
+
*/
|
|
546
|
+
getActiveRoute() {
|
|
547
|
+
return this.activePage?.getNormalizedRoute() || ''
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Get the first segment of the active route.
|
|
552
|
+
* Useful for root-level navigation highlighting.
|
|
553
|
+
*
|
|
554
|
+
* @returns {string} First segment (e.g., 'docs' for 'docs/getting-started')
|
|
555
|
+
*
|
|
556
|
+
* @example
|
|
557
|
+
* // Active route: 'docs/getting-started/installation'
|
|
558
|
+
* website.getActiveRootSegment() // 'docs'
|
|
559
|
+
*/
|
|
560
|
+
getActiveRootSegment() {
|
|
561
|
+
return this.getActiveRoute().split('/')[0]
|
|
562
|
+
}
|
|
531
563
|
}
|