@uniweb/core 0.4.2 → 0.4.4

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/core",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "Core classes for the Uniweb platform - Uniweb, Website, Page, Block",
5
5
  "type": "module",
6
6
  "exports": {
package/src/uniweb.js CHANGED
@@ -16,6 +16,7 @@ export default class Uniweb {
16
16
  this.foundation = null // The loaded foundation module
17
17
  this.foundationConfig = {} // Configuration from foundation (capabilities)
18
18
  this.meta = {} // Per-component runtime metadata (from meta.js)
19
+ this.extensions = [] // Array of { foundation, meta } objects
19
20
  this.language = 'en'
20
21
 
21
22
  // Icon resolver: (library, name) => Promise<string|null>
@@ -67,13 +68,30 @@ export default class Uniweb {
67
68
  }
68
69
  }
69
70
 
71
+ /**
72
+ * Register an extension (secondary foundation)
73
+ * @param {Object} foundation - The loaded ESM extension module
74
+ */
75
+ registerExtension(foundation) {
76
+ const meta = foundation.meta || {}
77
+ this.extensions.push({ foundation, meta })
78
+ }
79
+
70
80
  /**
71
81
  * Get runtime metadata for a component
72
82
  * @param {string} componentName
73
83
  * @returns {Object|null} Meta with defaults, context, initialState, background, data
74
84
  */
75
85
  getComponentMeta(componentName) {
76
- return this.meta[componentName] || null
86
+ const primary = this.meta[componentName]
87
+ if (primary) return primary
88
+
89
+ for (const ext of this.extensions) {
90
+ const meta = ext.meta[componentName]
91
+ if (meta) return meta
92
+ }
93
+
94
+ return null
77
95
  }
78
96
 
79
97
  /**
@@ -82,7 +100,7 @@ export default class Uniweb {
82
100
  * @returns {Object} Default values (empty object if none)
83
101
  */
84
102
  getComponentDefaults(componentName) {
85
- return this.meta[componentName]?.defaults || {}
103
+ return this.getComponentMeta(componentName)?.defaults || {}
86
104
  }
87
105
 
88
106
  /**
@@ -96,8 +114,17 @@ export default class Uniweb {
96
114
  return undefined
97
115
  }
98
116
 
99
- // Look in components object first, then direct access (named export)
100
- return this.foundation.components?.[name] || this.foundation[name]
117
+ // Primary foundation first
118
+ const primary = this.foundation.components?.[name] || this.foundation[name]
119
+ if (primary) return primary
120
+
121
+ // Fall through to extensions (declared order)
122
+ for (const ext of this.extensions) {
123
+ const component = ext.foundation.components?.[name] || ext.foundation[name]
124
+ if (component) return component
125
+ }
126
+
127
+ return undefined
101
128
  }
102
129
 
103
130
  /**
@@ -105,14 +132,23 @@ export default class Uniweb {
105
132
  * @returns {string[]}
106
133
  */
107
134
  listComponents() {
108
- if (!this.foundation) return []
135
+ const names = new Set()
136
+
137
+ if (this.foundation?.components) {
138
+ for (const name of Object.keys(this.foundation.components)) {
139
+ names.add(name)
140
+ }
141
+ }
109
142
 
110
- // Use components object if available
111
- if (this.foundation.components) {
112
- return Object.keys(this.foundation.components)
143
+ for (const ext of this.extensions) {
144
+ if (ext.foundation.components) {
145
+ for (const name of Object.keys(ext.foundation.components)) {
146
+ names.add(name)
147
+ }
148
+ }
113
149
  }
114
150
 
115
- return []
151
+ return [...names]
116
152
  }
117
153
 
118
154
  /**
package/src/website.js CHANGED
@@ -18,21 +18,19 @@ export default class Website {
18
18
  this.description = config.description || ''
19
19
  this.url = config.url || ''
20
20
 
21
- // Store special pages (layout areas)
21
+ // Store layout panels (header, footer, left, right)
22
22
  // These come from top-level properties set by content-collector
23
- // Fallback to searching pages array for backwards compatibility
24
- this.headerPage = header || pages.find((p) => p.route === '/@header') || null
25
- this.footerPage = footer || pages.find((p) => p.route === '/@footer') || null
26
- this.leftPage = left || pages.find((p) => p.route === '/@left') || null
27
- this.rightPage = right || pages.find((p) => p.route === '/@right') || null
23
+ this.headerPage = header || null
24
+ this.footerPage = footer || null
25
+ this.leftPage = left || null
26
+ this.rightPage = right || null
28
27
 
29
28
  // Store 404 page (for SPA routing)
30
29
  // Convention: pages/404/ directory
31
30
  this.notFoundPage = notFound || pages.find((p) => p.route === '/404') || null
32
31
 
33
- // Filter out special pages from regular pages array
34
- const specialRoutes = ['/@header', '/@footer', '/@left', '/@right', '/404']
35
- const regularPages = pages.filter((page) => !specialRoutes.includes(page.route))
32
+ // Filter out 404 from regular pages array
33
+ const regularPages = pages.filter((page) => page.route !== '/404')
36
34
 
37
35
  // Store original page data for dynamic pages (needed to create instances on-demand)
38
36
  this._dynamicPageData = new Map()
@@ -792,9 +790,6 @@ export default class Website {
792
790
 
793
791
  // Filter pages based on navigation type and visibility
794
792
  const isPageVisible = (page) => {
795
- // Always exclude special pages (header/footer are already separated)
796
- if (page.route.startsWith('/@')) return false
797
-
798
793
  // Always exclude dynamic route template pages (e.g., /blog/:slug)
799
794
  // These are templates for generating pages, not actual navigable pages
800
795
  if (page.route.includes(':')) return false