metaowl 0.2.9 → 0.2.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.
@@ -397,6 +397,39 @@ router.beforeEach((to, from, next) => {
397
397
  - File: pages/blog/[slug]/Blog.js → URL: /blog/:slug
398
398
  - File: pages/[...path]/NotFound.js → URL: /:path(.*)
399
399
 
400
+ ### Layouts
401
+ Share page structures across routes with automatic layout resolution:
402
+
403
+ \`\`\`javascript
404
+ // pages/admin/Dashboard.js
405
+ export class DashboardPage extends Component {
406
+ static template = 'DashboardPage'
407
+ static layout = 'admin' // Use admin layout
408
+ }
409
+ \`\`\`
410
+
411
+ Layout Directory:
412
+ \`\`\`
413
+ src/
414
+ ├── layouts/
415
+ │ ├── default/
416
+ │ │ ├── DefaultLayout.js
417
+ │ │ └── DefaultLayout.xml
418
+ │ └── admin/
419
+ │ ├── AdminLayout.js
420
+ │ └── AdminLayout.xml
421
+ \`\`\`
422
+
423
+ Layout Template uses \`<t t-slot=\"default\"/>\` to render page content:
424
+ \`\`\`xml
425
+ <t t-name="AdminLayout">
426
+ <div class="layout-admin">
427
+ <aside>Sidebar</aside>
428
+ <main><t t-slot="default"/></main>
429
+ </div>
430
+ </t>
431
+ \`\`\`
432
+
400
433
  ### API Requests
401
434
  \`\`\`javascript
402
435
  import { Fetch } from 'metaowl'
@@ -521,6 +554,24 @@ router.push('/path')
521
554
  router.back()
522
555
  \`\`\`
523
556
 
557
+ ### Layouts
558
+ Shared page structures with automatic resolution.
559
+ \`\`\`javascript
560
+ // In page component
561
+ export class DashboardPage extends Component {
562
+ static template = 'DashboardPage'
563
+ static layout = 'admin' // Use admin layout
564
+ }
565
+ \`\`\`
566
+
567
+ // Layout component uses t-slot for page content:
568
+ // <t t-slot="default"/>
569
+
570
+ Layout functions:
571
+ - registerLayout(name, Component) - Register a layout
572
+ - resolveLayout(Component, path?) - Get layout for component
573
+ - setRouteLayout(path, name) - Assign layout to route
574
+
524
575
  ## Component Template
525
576
  \`\`\`javascript
526
577
  import { Component } from '@odoo/owl'
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import { mount } from '@odoo/owl'
9
9
  import { mergeTemplates } from './templates-manager.js'
10
+ import { resolveLayout, getLayout, mountWithLayout } from './layouts.js'
10
11
 
11
12
  const _defaults = {
12
13
  warnIfNoStaticProps: true,
@@ -43,5 +44,18 @@ export async function mountApp(route) {
43
44
  const mountElement = document.getElementById('metaowl')
44
45
  mountElement.innerHTML = ''
45
46
 
46
- await mount(route[0].component, mountElement, { ..._config, templates })
47
+ const pageComponent = route[0].component
48
+ const pagePath = route[0].path
49
+
50
+ // Check for layout
51
+ const layoutName = resolveLayout(pageComponent, pagePath)
52
+ const LayoutClass = getLayout(layoutName)
53
+
54
+ if (LayoutClass) {
55
+ // Mount with layout
56
+ await mountWithLayout(pageComponent, mountElement, { routePath: pagePath, templates })
57
+ } else {
58
+ // Mount without layout
59
+ await mount(pageComponent, mountElement, { ..._config, templates })
60
+ }
47
61
  }
@@ -272,7 +272,7 @@ export function createLayoutWrapper(layoutComponent, pageComponent, props = {})
272
272
  * @returns {Promise<Component>} Mounted component instance
273
273
  */
274
274
  export async function mountWithLayout(pageComponent, target, options = {}, config = {}) {
275
- const { routePath, props = {} } = options
275
+ const { routePath, props = {}, templates } = options
276
276
 
277
277
  const layoutName = resolveLayout(pageComponent, routePath)
278
278
  const LayoutClass = getLayout(layoutName)
@@ -280,14 +280,14 @@ export async function mountWithLayout(pageComponent, target, options = {}, confi
280
280
  if (!LayoutClass) {
281
281
  console.warn(`[metaowl] Layout "${layoutName}" not found, mounting page without layout`)
282
282
  const { mount } = await import('@odoo/owl')
283
- return mount(pageComponent, target, { ...config, props })
283
+ return mount(pageComponent, target, { ...config, props, templates })
284
284
  }
285
285
 
286
286
  // Create wrapper that combines layout and page
287
287
  const WrapperClass = createLayoutWrapper(LayoutClass, pageComponent, props)
288
288
 
289
289
  const { mount } = await import('@odoo/owl')
290
- const instance = await mount(WrapperClass, target, config)
290
+ const instance = await mount(WrapperClass, target, { ...config, templates })
291
291
 
292
292
  _currentLayout = instance
293
293
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metaowl",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "Lightweight meta-framework for Odoo OWL — file-based routing, app mounting, Fetch helper, Cache, Meta tags, SSG generator, and a Vite plugin.",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/vite/plugin.js CHANGED
@@ -35,6 +35,7 @@ function collectXml(globPattern) {
35
35
  * @param {string} [options.publicDir='../public'] - Public assets directory.
36
36
  * @param {string} [options.componentsDir='src/components'] - OWL components directory.
37
37
  * @param {string} [options.pagesDir='src/pages'] - OWL pages directory.
38
+ * @param {string} [options.layoutsDir='src/layouts'] - OWL layouts directory.
38
39
  * @param {string[]} [options.restartGlobs] - Additional globs that trigger dev-server restart.
39
40
  * @param {string} [options.frameworkEntry] - Framework entry for manual chunk.
40
41
  * @param {string[]} [options.vendorPackages] - npm packages bundled into the vendor chunk.
@@ -51,6 +52,7 @@ export async function metaowlPlugin(options = {}) {
51
52
  publicDir = '../public',
52
53
  componentsDir = 'src/components',
53
54
  pagesDir = 'src/pages',
55
+ layoutsDir = 'src/layouts',
54
56
  restartGlobs = [],
55
57
  frameworkEntry = './node_modules/metaowl/index.js',
56
58
  vendorPackages = ['@odoo/owl'],
@@ -60,7 +62,8 @@ export async function metaowlPlugin(options = {}) {
60
62
 
61
63
  const componentXml = collectXml(`${componentsDir}/**/*.xml`)
62
64
  const pageXml = collectXml(`${pagesDir}/**/*.xml`)
63
- const allComponents = [...pageXml, ...componentXml]
65
+ const layoutXml = collectXml(`${layoutsDir}/**/*.xml`)
66
+ const allComponents = [...pageXml, ...componentXml, ...layoutXml]
64
67
 
65
68
  const defaultRestartGlobs = [
66
69
  `${root}/**/*.[jt]s`,
@@ -169,7 +172,8 @@ export async function metaowlPlugin(options = {}) {
169
172
  include: ['@odoo/owl'],
170
173
  entries: [
171
174
  `${componentsDir}/**/*.[jt]s`,
172
- `${pagesDir}/**/*.[jt]s`
175
+ `${pagesDir}/**/*.[jt]s`,
176
+ `${layoutsDir}/**/*.[jt]s`
173
177
  ],
174
178
  ...(cfg.optimizeDeps ?? {})
175
179
  }
@@ -183,10 +187,14 @@ export async function metaowlPlugin(options = {}) {
183
187
  transform(code, id) {
184
188
  if (!id.endsWith('/metaowl.js')) return
185
189
  const pagesRel = pagesDir.replace(new RegExp(`^${root}[\\/]`), '')
190
+ const layoutsRel = layoutsDir.replace(new RegExp(`^${root}[\\/]`), '')
186
191
  return {
187
192
  code: code.replace(
188
193
  /boot\(\s*\)/,
189
194
  `boot(import.meta.glob('./${pagesRel}/**/*.js', { eager: true }))`
195
+ ).replace(
196
+ /discoverLayouts\(\s*\)/,
197
+ `discoverLayouts(import.meta.glob('./${layoutsRel}/**/*.js', { eager: true }))`
190
198
  ),
191
199
  map: null
192
200
  }
@@ -198,10 +206,12 @@ export async function metaowlPlugin(options = {}) {
198
206
  if (!id.endsWith('/css.js')) return
199
207
  const compRel = componentsDir.replace(new RegExp(`^${root}[\\/]`), '')
200
208
  const pagesRel = pagesDir.replace(new RegExp(`^${root}[\\/]`), '')
209
+ const layoutsRel = layoutsDir.replace(new RegExp(`^${root}[\\/]`), '')
201
210
  return {
202
211
  code: code + '\n' +
203
212
  `import.meta.glob('/${compRel}/**/*.{css,scss}', { eager: true })\n` +
204
- `import.meta.glob('/${pagesRel}/**/*.{css,scss}', { eager: true })\n`,
213
+ `import.meta.glob('/${pagesRel}/**/*.{css,scss}', { eager: true })\n` +
214
+ `import.meta.glob('/${layoutsRel}/**/*.{css,scss}', { eager: true })\n`,
205
215
  map: null
206
216
  }
207
217
  }
@@ -213,7 +223,7 @@ export async function metaowlPlugin(options = {}) {
213
223
  const projectRoot = process.cwd()
214
224
 
215
225
  // Copy OWL XML templates (loaded at runtime via fetch — not processed by Vite)
216
- const xmlFiles = globSync([`${componentsDir}/**/*.xml`, `${pagesDir}/**/*.xml`])
226
+ const xmlFiles = globSync([`${componentsDir}/**/*.xml`, `${pagesDir}/**/*.xml`, `${layoutsDir}/**/*.xml`])
217
227
  for (const xmlFile of xmlFiles) {
218
228
  const relPath = xmlFile.replace(new RegExp(`^${root}[\\/]`), '')
219
229
  const dest = resolve(_outDirResolved, relPath)