zero-query 0.8.6 → 0.8.8

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 CHANGED
@@ -65,7 +65,7 @@ The dev server includes a **full-screen error overlay** that surfaces errors dir
65
65
 
66
66
  #### Floating Toolbar & Inspector
67
67
 
68
- A compact toolbar appears in the bottom-right corner showing live request/render counters and a DOM button. Click any counter to open a **dark-themed DevTools inspector** as a popup — or visit `http://localhost:<port>/_devtools` for a standalone split-view panel with four tabs: **Elements** (live DOM tree with component badges, source viewer, expand/collapse), **Network** (fetch log with JSON viewer), **Components** (live state cards), and **Performance** (render timeline with timing metrics).
68
+ A compact expandable toolbar appears in the bottom-right corner. In its **collapsed** state it shows live render and request counters. Click the chevron to **expand** and reveal the route indicator (color-coded by the last navigation event — navigate, pop, replace, hashchange, substate), registered component count, and DOM element count. Click any stat to open a **dark-themed DevTools inspector** as a popup — or visit `http://localhost:<port>/_devtools` for a standalone split-view panel with five tabs: **Router** (live route state, guards, history timeline), **Components** (live state cards), **Performance** (render timeline with timing metrics), **Network** (fetch log with JSON viewer), and **Elements** (live DOM tree with component badges, source viewer, expand/collapse).
69
69
 
70
70
  ### Alternative: Manual Setup (No npm)
71
71
 
@@ -189,13 +189,12 @@ Output goes to `dist/` next to your `index.html`:
189
189
  dist/
190
190
  server/ ← deploy to your web server (<base href="/"> for SPA routes)
191
191
  index.html
192
- z-app.<hash>.js
193
192
  z-app.<hash>.min.js
194
193
  global.<hash>.min.css
195
194
  assets/
196
195
  local/ ← open from disk (file://) — no server needed
197
196
  index.html
198
- z-app.<hash>.js
197
+ z-app.<hash>.min.js
199
198
  ...
200
199
  ```
201
200
 
@@ -217,7 +216,7 @@ dist/
217
216
  4. **Convention fallbacks** — `app/app.js`, `scripts/app.js`, `src/app.js`, `js/app.js`, `app.js`, `main.js`.
218
217
  2. Resolves all `import` statements and topologically sorts dependencies
219
218
  3. Strips `import`/`export` syntax, wraps in an IIFE
220
- 4. Embeds zQuery library and inlines `templateUrl` / `styleUrl` / `pages` files
219
+ 4. Embeds zQuery library and inlines `templateUrl` / `styleUrl` files
221
220
  5. Rewrites HTML, copies assets, produces hashed filenames
222
221
 
223
222
  ---
@@ -277,7 +276,7 @@ location / {
277
276
  | CLI Command | Description |
278
277
  | --- | --- |
279
278
  | `zquery create [dir]` | Scaffold a new project (index.html, components, store, styles) |
280
- | `zquery dev [root]` | Dev server with live-reload, CSS hot-swap, error overlay, floating toolbar &amp; inspector panel (port 3100). Visit `/_devtools` for the standalone panel. `--index` for custom HTML, `--bundle` for bundled mode, `--no-intercept` to skip CDN intercept. |
279
+ | `zquery dev [root]` | Dev server with live-reload, CSS hot-swap, error overlay, expandable floating toolbar &amp; five-tab inspector panel (port 3100). Visit `/_devtools` for the standalone panel. `--index` for custom HTML, `--bundle` for bundled mode, `--no-intercept` to skip CDN intercept. |
281
280
  | `zquery bundle [dir\|file]` | Bundle app into a single IIFE file. Accepts dir or direct entry file. |
282
281
  | `zquery build` | Build the zQuery library (`dist/zquery.min.js`) |
283
282
  | `zquery --help` | Show CLI usage |
@@ -353,7 +353,7 @@ function _collapseTemplateCSS(tpl) {
353
353
 
354
354
  /**
355
355
  * Scan bundled source files for external resource references
356
- * (pages config, templateUrl, styleUrl) and return a map of
356
+ * (templateUrl, styleUrl) and return a map of
357
357
  * { relativePath: fileContent } for inlining.
358
358
  */
359
359
  function collectInlineResources(files, projectRoot) {
@@ -363,33 +363,6 @@ function collectInlineResources(files, projectRoot) {
363
363
  const code = fs.readFileSync(file, 'utf-8');
364
364
  const fileDir = path.dirname(file);
365
365
 
366
- // pages: config
367
- const pagesMatch = code.match(/pages\s*:\s*\{[^}]*dir\s*:\s*['"]([^'"]+)['"]/s);
368
- if (pagesMatch) {
369
- const pagesDir = pagesMatch[1];
370
- const ext = (code.match(/pages\s*:\s*\{[^}]*ext\s*:\s*['"]([^'"]+)['"]/s) || [])[1] || '.html';
371
- const itemsMatch = code.match(/items\s*:\s*\[([\s\S]*?)\]/);
372
- if (itemsMatch) {
373
- const itemsBlock = itemsMatch[1];
374
- const ids = [];
375
- let m;
376
- const strRe = /['"]([^'"]+)['"]/g;
377
- while ((m = strRe.exec(itemsBlock)) !== null) {
378
- const before = itemsBlock.substring(Math.max(0, m.index - 20), m.index);
379
- if (/label\s*:\s*$/.test(before)) continue;
380
- ids.push(m[1]);
381
- }
382
- const absPagesDir = path.join(fileDir, pagesDir);
383
- for (const id of ids) {
384
- const pagePath = path.join(absPagesDir, id + ext);
385
- if (fs.existsSync(pagePath)) {
386
- const relKey = path.relative(projectRoot, pagePath).replace(/\\/g, '/');
387
- inlineMap[relKey] = fs.readFileSync(pagePath, 'utf-8');
388
- }
389
- }
390
- }
391
- }
392
-
393
366
  // styleUrl:
394
367
  const styleUrlRe = /styleUrl\s*:\s*['"]([^'"]+)['"]/g;
395
368
  const styleMatch = styleUrlRe.exec(code);
@@ -409,6 +382,25 @@ function collectInlineResources(files, projectRoot) {
409
382
  const relKey = path.relative(projectRoot, tmplPath).replace(/\\/g, '/');
410
383
  inlineMap[relKey] = fs.readFileSync(tmplPath, 'utf-8');
411
384
  }
385
+ } else if (/templateUrl\s*:/.test(code)) {
386
+ // Dynamic templateUrl (e.g. Object.fromEntries, computed map) —
387
+ // inline all .html files in the component's directory tree so
388
+ // the runtime __zqInline lookup can resolve them by suffix.
389
+ (function scanHtml(dir) {
390
+ try {
391
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
392
+ const full = path.join(dir, entry.name);
393
+ if (entry.isFile() && entry.name.endsWith('.html')) {
394
+ const relKey = path.relative(projectRoot, full).replace(/\\/g, '/');
395
+ if (!inlineMap[relKey]) {
396
+ inlineMap[relKey] = fs.readFileSync(full, 'utf-8');
397
+ }
398
+ } else if (entry.isDirectory()) {
399
+ scanHtml(full);
400
+ }
401
+ }
402
+ } catch { /* permission error — skip */ }
403
+ })(fileDir);
412
404
  }
413
405
  }
414
406
 
@@ -9,7 +9,7 @@
9
9
  <button class="viewport-btn" data-width="768" title="Tablet (768px)"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="2" width="16" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12" y2="18"/></svg></button>
10
10
  <button class="viewport-btn active" data-width="0" title="Desktop (fill)"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg></button>
11
11
  <span class="divider-sep"></span>
12
- <button id="btn-route" title="Current route"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="7" y1="2" x2="17" y2="22"/></svg></button>
12
+ <button id="btn-route" title="Current route"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="17" y1="2" x2="7" y2="22"/></svg></button>
13
13
  <span class="route-label" id="route-label">/</span>
14
14
  </div>
15
15
  </div>
@@ -558,6 +558,7 @@ const OVERLAY_SCRIPT = `<script>
558
558
  if (__zqChannel) {
559
559
  try { __zqChannel.postMessage({ type: 'router', data: evt }); } catch(e) {}
560
560
  }
561
+ updateDevBar();
561
562
  };
562
563
 
563
564
  var _origReplaceState = history.replaceState;
@@ -576,6 +577,7 @@ const OVERLAY_SCRIPT = `<script>
576
577
  if (__zqChannel) {
577
578
  try { __zqChannel.postMessage({ type: 'router', data: evt }); } catch(e) {}
578
579
  }
580
+ updateDevBar();
579
581
  };
580
582
 
581
583
  window.addEventListener('popstate', function(e) {
@@ -593,6 +595,7 @@ const OVERLAY_SCRIPT = `<script>
593
595
  if (__zqChannel) {
594
596
  try { __zqChannel.postMessage({ type: 'router', data: evt }); } catch(e) {}
595
597
  }
598
+ updateDevBar();
596
599
  });
597
600
 
598
601
  window.addEventListener('hashchange', function() {
@@ -606,36 +609,62 @@ const OVERLAY_SCRIPT = `<script>
606
609
  if (__zqChannel) {
607
610
  try { __zqChannel.postMessage({ type: 'router', data: evt }); } catch(e) {}
608
611
  }
612
+ updateDevBar();
609
613
  });
610
614
 
611
615
  // =====================================================================
612
- // Dev Toolbar — floating bar with DOM viewer button & request counter
616
+ // Dev Toolbar — expandable floating bar with stats
613
617
  // =====================================================================
614
618
  var devBar;
619
+ var __zqBarExpanded = false;
620
+ var __zqRouteColors = {
621
+ navigate: { bg: 'rgba(63,185,80,0.12)', fg: '#3fb950' },
622
+ replace: { bg: 'rgba(210,153,34,0.12)', fg: '#d29922' },
623
+ pop: { bg: 'rgba(248,81,73,0.12)', fg: '#f85149' },
624
+ 'pop-substate':{ bg: 'rgba(248,81,73,0.12)', fg: '#f85149' },
625
+ substate: { bg: 'rgba(168,130,255,0.12)', fg: '#a882ff' },
626
+ hashchange: { bg: 'rgba(88,166,255,0.12)', fg: '#58a6ff' }
627
+ };
628
+ var __zqRouteDefault = { bg: 'rgba(227,155,55,0.12)', fg: '#e39b37' };
629
+ var __zqRouteFadeTimer = null;
615
630
 
616
631
  function createDevBar() {
617
632
  devBar = document.createElement('div');
618
633
  devBar.id = '__zq_devbar';
619
634
  devBar.setAttribute('style',
620
635
  'position:fixed;bottom:12px;right:12px;z-index:2147483646;' +
621
- 'display:flex;align-items:center;gap:6px;' +
636
+ 'display:flex;align-items:center;gap:4px;' +
622
637
  'background:rgba(22,27,34,0.92);border:1px solid rgba(48,54,61,0.8);' +
623
638
  'border-radius:8px;padding:4px 6px;backdrop-filter:blur(8px);' +
624
639
  'font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;' +
625
640
  'font-size:11px;color:#8b949e;user-select:none;cursor:default;' +
626
- 'box-shadow:0 4px 12px rgba(0,0,0,0.4);'
641
+ 'box-shadow:0 4px 12px rgba(0,0,0,0.4);transition:all .25s cubic-bezier(.22,1,.36,1);'
627
642
  );
643
+
644
+ var statStyle = 'padding:2px 6px;border-radius:4px;font-size:10px;font-weight:600;cursor:pointer;white-space:nowrap;';
645
+ var expandedStyle = statStyle + 'display:none;transform:scale(0);opacity:0;transform-origin:left center;will-change:transform,opacity;transition:transform .25s cubic-bezier(.22,1,.36,1),opacity .2s ease;';
646
+
628
647
  devBar.innerHTML =
629
648
  '<span style="color:#58a6ff;font-weight:700;padding:0 4px;font-size:10px;letter-spacing:.5px">zQ</span>' +
630
- '<span id="__zq_bar_reqs" title="Network requests" style="padding:2px 6px;border-radius:4px;' +
631
- 'background:rgba(88,166,255,0.1);color:#58a6ff;cursor:pointer;font-size:10px;font-weight:600;">0 req</span>' +
632
- '<span id="__zq_bar_morphs" title="Render operations" style="padding:2px 6px;border-radius:4px;' +
633
- 'background:rgba(188,140,255,0.1);color:#bc8cff;cursor:pointer;font-size:10px;font-weight:600;">0 render</span>' +
634
- '<button id="__zq_bar_dom" title="Open DevTools (/_devtools)" style="' +
635
- 'padding:3px 8px;border-radius:4px;font-size:10px;font-weight:700;' +
636
- 'background:rgba(63,185,80,0.15);color:#3fb950;border:1px solid rgba(63,185,80,0.3);' +
637
- 'cursor:pointer;font-family:inherit;transition:all .15s;' +
638
- '">DOM</button>' +
649
+ // Expanded-only stats
650
+ '<span id="__zq_bar_route" class="__zq_ex" title="Current route" style="' + expandedStyle +
651
+ 'background:rgba(227,155,55,0.12);color:#e39b37;outline-offset:1px;transition:transform .25s cubic-bezier(.22,1,.36,1),opacity .2s ease,background .3s ease,color .3s ease,outline-color .6s ease;">/</span>' +
652
+ '<span id="__zq_bar_comps" class="__zq_ex" title="Registered components" style="' + expandedStyle +
653
+ 'background:rgba(168,130,255,0.1);color:#a882ff;">0 comps</span>' +
654
+ // Always-visible stats
655
+ '<span id="__zq_bar_morphs" title="Render operations" style="' + statStyle +
656
+ 'background:rgba(188,140,255,0.1);color:#bc8cff;">0 render</span>' +
657
+ '<span id="__zq_bar_reqs" title="Network requests" style="' + statStyle +
658
+ 'background:rgba(88,166,255,0.1);color:#58a6ff;">0 req</span>' +
659
+ // Expanded-only stats
660
+ '<span id="__zq_bar_els" class="__zq_ex" title="DOM elements" style="' + expandedStyle +
661
+ 'background:rgba(63,185,80,0.1);color:#3fb950;">0 els</span>' +
662
+ // Toggle expand/collapse
663
+ '<button id="__zq_bar_toggle" title="Expand toolbar" style="' +
664
+ 'padding:2px 6px;border-radius:4px;font-size:10px;font-weight:600;' +
665
+ 'background:rgba(88,166,255,0.08);color:#58a6ff;border:1px solid rgba(88,166,255,0.2);' +
666
+ 'cursor:pointer;font-family:inherit;transition:all .15s;line-height:1;' +
667
+ '"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 6 9 12 15 18"/></svg></button>' +
639
668
  '<button id="__zq_bar_close" title="Close toolbar" style="' +
640
669
  'padding:0 4px;color:#484f58;cursor:pointer;font-size:14px;border:none;' +
641
670
  'background:none;font-family:inherit;line-height:1;' +
@@ -657,28 +686,9 @@ const OVERLAY_SCRIPT = `<script>
657
686
  }
658
687
  }
659
688
 
660
- // Req counter → Network tab
661
- document.getElementById('__zq_bar_reqs').addEventListener('click', function() {
662
- if (isInSplitFrame()) {
663
- switchDevTab('network');
664
- } else {
665
- openDevToolsPopup('network');
666
- }
667
- });
668
-
669
- // Render counter → Performance tab
670
- document.getElementById('__zq_bar_morphs').addEventListener('click', function() {
671
- if (isInSplitFrame()) {
672
- switchDevTab('perf');
673
- } else {
674
- openDevToolsPopup('perf');
675
- }
676
- });
677
-
678
- // DOM button → Elements tab (in split) or open popup
689
+ // Open devtools popup
679
690
  var __zqPopup = null;
680
691
  function openDevToolsPopup(tab) {
681
- // If popup is already open, just switch the tab via BroadcastChannel
682
692
  if (__zqPopup && !__zqPopup.closed) {
683
693
  switchDevTab(tab);
684
694
  __zqPopup.focus();
@@ -693,26 +703,60 @@ const OVERLAY_SCRIPT = `<script>
693
703
  ',resizable=yes,scrollbars=yes');
694
704
  }
695
705
 
696
- document.getElementById('__zq_bar_dom').addEventListener('click', function() {
697
- if (isInSplitFrame()) {
698
- switchDevTab('dom');
706
+ function openTab(tab) {
707
+ if (isInSplitFrame()) { switchDevTab(tab); } else { openDevToolsPopup(tab); }
708
+ }
709
+
710
+ // Stat click handlers → open relevant devtools tab
711
+ document.getElementById('__zq_bar_route').addEventListener('click', function() { openTab('router'); });
712
+ document.getElementById('__zq_bar_comps').addEventListener('click', function() { openTab('components'); });
713
+ document.getElementById('__zq_bar_morphs').addEventListener('click', function() { openTab('perf'); });
714
+ document.getElementById('__zq_bar_reqs').addEventListener('click', function() { openTab('network'); });
715
+ document.getElementById('__zq_bar_els').addEventListener('click', function() { openTab('dom'); });
716
+
717
+ // Expand / collapse toggle
718
+ document.getElementById('__zq_bar_toggle').addEventListener('click', function() {
719
+ __zqBarExpanded = !__zqBarExpanded;
720
+ var items = devBar.querySelectorAll('.__zq_ex');
721
+ var btn = this;
722
+ if (__zqBarExpanded) {
723
+ btn.innerHTML = '<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 6 15 12 9 18"/></svg>';
724
+ btn.title = 'Collapse toolbar';
725
+ for (var i = 0; i < items.length; i++) {
726
+ items[i].style.display = 'inline';
727
+ items[i].offsetWidth; // reflow
728
+ items[i].style.transform = 'scale(1)';
729
+ items[i].style.opacity = '1';
730
+ }
731
+ updateDevBar();
699
732
  } else {
700
- openDevToolsPopup('dom');
733
+ btn.innerHTML = '<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 6 9 12 15 18"/></svg>';
734
+ btn.title = 'Expand toolbar';
735
+ for (var i = 0; i < items.length; i++) {
736
+ items[i].style.transform = 'scale(0)';
737
+ items[i].style.opacity = '0';
738
+ }
739
+ setTimeout(function() {
740
+ if (!__zqBarExpanded) {
741
+ var items = devBar.querySelectorAll('.__zq_ex');
742
+ for (var i = 0; i < items.length; i++) items[i].style.display = 'none';
743
+ }
744
+ }, 250);
701
745
  }
702
746
  });
703
747
 
748
+ // Toggle hover
749
+ document.getElementById('__zq_bar_toggle').addEventListener('mouseover', function() {
750
+ this.style.background = 'rgba(88,166,255,0.18)';
751
+ });
752
+ document.getElementById('__zq_bar_toggle').addEventListener('mouseout', function() {
753
+ this.style.background = 'rgba(88,166,255,0.08)';
754
+ });
755
+
704
756
  // Close button
705
757
  document.getElementById('__zq_bar_close').addEventListener('click', function() {
706
758
  devBar.style.display = 'none';
707
759
  });
708
-
709
- // Hover effects
710
- document.getElementById('__zq_bar_dom').addEventListener('mouseover', function() {
711
- this.style.background = 'rgba(63,185,80,0.3)';
712
- });
713
- document.getElementById('__zq_bar_dom').addEventListener('mouseout', function() {
714
- this.style.background = 'rgba(63,185,80,0.15)';
715
- });
716
760
  }
717
761
 
718
762
  function updateDevBar() {
@@ -721,6 +765,50 @@ const OVERLAY_SCRIPT = `<script>
721
765
  var morphEl = document.getElementById('__zq_bar_morphs');
722
766
  if (reqEl) reqEl.textContent = __zqRequests.length + ' req';
723
767
  if (morphEl) morphEl.textContent = __zqMorphCount + ' render';
768
+
769
+ if (__zqBarExpanded) {
770
+ var routeEl = document.getElementById('__zq_bar_route');
771
+ var compsEl = document.getElementById('__zq_bar_comps');
772
+ var elsEl = document.getElementById('__zq_bar_els');
773
+ if (routeEl) {
774
+ var lastEvt = __zqRouterEvents.length ? __zqRouterEvents[__zqRouterEvents.length - 1] : null;
775
+ var action = lastEvt ? lastEvt.action : '';
776
+ var path = location.pathname + location.hash;
777
+ if (path.length > 16) path = path.substring(0, 14) + '…';
778
+ var colors = __zqRouteColors[action] || __zqRouteDefault;
779
+ routeEl.style.background = colors.bg;
780
+ routeEl.style.color = colors.fg;
781
+ if (action) {
782
+ var label = action === 'pop-substate' ? 'pop' : action;
783
+ routeEl.textContent = label + ' ' + path;
784
+ routeEl.title = action + ' → ' + location.pathname + location.hash;
785
+ } else {
786
+ routeEl.textContent = path;
787
+ }
788
+ // Flash brightness on fresh events
789
+ if (lastEvt && (Date.now() - lastEvt.timestamp) < 2000) {
790
+ routeEl.style.outline = '1px solid ' + colors.fg;
791
+ clearTimeout(__zqRouteFadeTimer);
792
+ __zqRouteFadeTimer = setTimeout(function() {
793
+ var el = document.getElementById('__zq_bar_route');
794
+ if (el) el.style.outline = 'none';
795
+ }, 1800);
796
+ } else {
797
+ routeEl.style.outline = 'none';
798
+ }
799
+ }
800
+ if (compsEl) {
801
+ var count = 0;
802
+ try {
803
+ if (window.$ && $.components) count = Object.keys($.components()).length;
804
+ else if (window.$ && $._components) count = Object.keys($._components).length;
805
+ } catch(e) {}
806
+ compsEl.textContent = count + ' comps';
807
+ }
808
+ if (elsEl) {
809
+ elsEl.textContent = document.querySelectorAll('*').length + ' els';
810
+ }
811
+ }
724
812
  }
725
813
 
726
814
  // Expose for devtools popup
Binary file
package/dist/zquery.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * zQuery (zeroQuery) v0.8.6
2
+ * zQuery (zeroQuery) v0.8.8
3
3
  * Lightweight Frontend Library
4
4
  * https://github.com/tonywied17/zero-query
5
5
  * (c) 2026 Anthony Wiedman - MIT License
@@ -2553,7 +2553,7 @@ function _getKey(node) {
2553
2553
  * - Scoped styles (inline or via styleUrl)
2554
2554
  * - External templates via templateUrl (with {{expression}} interpolation)
2555
2555
  * - External styles via styleUrl (fetched & scoped automatically)
2556
- * - Relative path resolution — templateUrl, styleUrl, and pages.dir
2556
+ * - Relative path resolution — templateUrl and styleUrl
2557
2557
  * resolve relative to the component file automatically
2558
2558
  */
2559
2559
 
@@ -2623,16 +2623,6 @@ function _fetchResource(url) {
2623
2623
  return promise;
2624
2624
  }
2625
2625
 
2626
- /**
2627
- * Convert a kebab-case id to Title Case.
2628
- * 'getting-started' → 'Getting Started'
2629
- * @param {string} id
2630
- * @returns {string}
2631
- */
2632
- function _titleCase(id) {
2633
- return id.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
2634
- }
2635
-
2636
2626
  /**
2637
2627
  * Resolve a relative URL against a base.
2638
2628
  *
@@ -2851,47 +2841,10 @@ class Component {
2851
2841
  // - string → single stylesheet
2852
2842
  // - string[] → array of URLs → all fetched & concatenated
2853
2843
  //
2854
- // pages config (shorthand for multi-template + route-param page switching):
2855
- // pages: {
2856
- // dir: 'pages', // relative to component file (or base)
2857
- // param: 'section', // route param name → this.activePage
2858
- // default: 'getting-started', // fallback when param is absent
2859
- // ext: '.html', // file extension (default '.html')
2860
- // items: ['page-a', { id: 'page-b', label: 'Page B' }, ...]
2861
- // }
2862
- // Exposes this.pages (array of {id,label}), this.activePage (current id)
2863
- // Pages are lazy-loaded: only the active page is fetched on first render,
2864
- // remaining pages are prefetched in the background for instant navigation.
2865
- //
2866
2844
  async _loadExternals() {
2867
2845
  const def = this._def;
2868
2846
  const base = def._base; // auto-detected or explicit
2869
2847
 
2870
- // -- Pages config ---------------------------------------------
2871
- if (def.pages && !def._pagesNormalized) {
2872
- const p = def.pages;
2873
- const ext = p.ext || '.html';
2874
- const dir = _resolveUrl((p.dir || '').replace(/\/+$/, ''), base);
2875
-
2876
- // Normalize items → [{id, label}, …]
2877
- def._pages = (p.items || []).map(item => {
2878
- if (typeof item === 'string') return { id: item, label: _titleCase(item) };
2879
- return { ...item, label: item.label || _titleCase(item.id) };
2880
- });
2881
-
2882
- // Build URL map for lazy per-page loading.
2883
- // Pages are fetched on demand (active page first, rest prefetched in
2884
- // the background) so the component renders as soon as the visible
2885
- // page is ready instead of waiting for every page to download.
2886
- def._pageUrls = {};
2887
- for (const { id } of def._pages) {
2888
- def._pageUrls[id] = `${dir}/${id}${ext}`;
2889
- }
2890
- if (!def._externalTemplates) def._externalTemplates = {};
2891
-
2892
- def._pagesNormalized = true;
2893
- }
2894
-
2895
2848
  // -- External templates --------------------------------------
2896
2849
  if (def.templateUrl && !def._templateLoaded) {
2897
2850
  const tu = def.templateUrl;
@@ -2904,9 +2857,8 @@ class Component {
2904
2857
  results.forEach((html, i) => { def._externalTemplates[i] = html; });
2905
2858
  } else if (typeof tu === 'object') {
2906
2859
  const entries = Object.entries(tu);
2907
- // Pages config already resolved; plain objects still need resolving
2908
2860
  const results = await Promise.all(
2909
- entries.map(([, url]) => _fetchResource(def._pagesNormalized ? url : _resolveUrl(url, base)))
2861
+ entries.map(([, url]) => _fetchResource(_resolveUrl(url, base)))
2910
2862
  );
2911
2863
  def._externalTemplates = {};
2912
2864
  entries.forEach(([key], i) => { def._externalTemplates[key] = results[i]; });
@@ -2935,8 +2887,7 @@ class Component {
2935
2887
  _render() {
2936
2888
  // If externals haven't loaded yet, trigger async load then re-render
2937
2889
  if ((this._def.templateUrl && !this._def._templateLoaded) ||
2938
- (this._def.styleUrl && !this._def._styleLoaded) ||
2939
- (this._def.pages && !this._def._pagesNormalized)) {
2890
+ (this._def.styleUrl && !this._def._styleLoaded)) {
2940
2891
  this._loadExternals().then(() => {
2941
2892
  if (!this._destroyed) this._render();
2942
2893
  });
@@ -2948,43 +2899,6 @@ class Component {
2948
2899
  this.templates = this._def._externalTemplates;
2949
2900
  }
2950
2901
 
2951
- // Expose pages metadata and active page (derived from route param)
2952
- if (this._def._pages) {
2953
- this.pages = this._def._pages;
2954
- const pc = this._def.pages;
2955
- let active = (pc.param && this.props.$params?.[pc.param]) || pc.default || this._def._pages[0]?.id || '';
2956
-
2957
- // Fall back to default if the param doesn't match any known page
2958
- if (this._def._pageUrls && !(active in this._def._pageUrls)) {
2959
- active = pc.default || this._def._pages[0]?.id || '';
2960
- }
2961
- this.activePage = active;
2962
-
2963
- // Lazy-load: fetch only the active page's template on demand
2964
- if (this._def._pageUrls && !(active in this._def._externalTemplates)) {
2965
- const url = this._def._pageUrls[active];
2966
- if (url) {
2967
- _fetchResource(url).then(html => {
2968
- this._def._externalTemplates[active] = html;
2969
- if (!this._destroyed) this._render();
2970
- });
2971
- return; // Wait for active page before rendering
2972
- }
2973
- }
2974
-
2975
- // Prefetch remaining pages in background (once, after active page is ready)
2976
- if (this._def._pageUrls && !this._def._pagesPrefetched) {
2977
- this._def._pagesPrefetched = true;
2978
- for (const [id, url] of Object.entries(this._def._pageUrls)) {
2979
- if (!(id in this._def._externalTemplates)) {
2980
- _fetchResource(url).then(html => {
2981
- this._def._externalTemplates[id] = html;
2982
- });
2983
- }
2984
- }
2985
- }
2986
- }
2987
-
2988
2902
  // Determine HTML content
2989
2903
  let html;
2990
2904
  if (this._def.render) {
@@ -3687,7 +3601,7 @@ class Component {
3687
3601
  // Reserved definition keys (not user methods)
3688
3602
  const _reservedKeys = new Set([
3689
3603
  'state', 'render', 'styles', 'init', 'mounted', 'updated', 'destroyed', 'props',
3690
- 'templateUrl', 'styleUrl', 'templates', 'pages', 'activePage', 'base',
3604
+ 'templateUrl', 'styleUrl', 'templates', 'base',
3691
3605
  'computed', 'watch'
3692
3606
  ]);
3693
3607
 
@@ -3710,8 +3624,8 @@ function component(name, definition) {
3710
3624
  }
3711
3625
  definition._name = name;
3712
3626
 
3713
- // Auto-detect the calling module's URL so that relative templateUrl,
3714
- // styleUrl, and pages.dir paths resolve relative to the component file.
3627
+ // Auto-detect the calling module's URL so that relative templateUrl
3628
+ // and styleUrl paths resolve relative to the component file.
3715
3629
  // An explicit `base` string on the definition overrides auto-detection.
3716
3630
  if (definition.base !== undefined) {
3717
3631
  definition._base = definition.base; // explicit override
@@ -3838,23 +3752,11 @@ async function prefetch(name) {
3838
3752
  const def = _registry.get(name);
3839
3753
  if (!def) return;
3840
3754
 
3841
- // Load templateUrl, styleUrl, and normalize pages config
3755
+ // Load templateUrl and styleUrl if not already loaded.
3842
3756
  if ((def.templateUrl && !def._templateLoaded) ||
3843
- (def.styleUrl && !def._styleLoaded) ||
3844
- (def.pages && !def._pagesNormalized)) {
3757
+ (def.styleUrl && !def._styleLoaded)) {
3845
3758
  await Component.prototype._loadExternals.call({ _def: def });
3846
3759
  }
3847
-
3848
- // For pages-based components, prefetch ALL page templates so any
3849
- // active page renders instantly on mount.
3850
- if (def._pageUrls && def._externalTemplates) {
3851
- const missing = Object.entries(def._pageUrls)
3852
- .filter(([id]) => !(id in def._externalTemplates));
3853
- if (missing.length) {
3854
- const results = await Promise.all(missing.map(([, url]) => _fetchResource(url)));
3855
- missing.forEach(([id], i) => { def._externalTemplates[id] = results[i]; });
3856
- }
3857
- }
3858
3760
  }
3859
3761
 
3860
3762
 
@@ -4098,7 +4000,7 @@ class Router {
4098
4000
 
4099
4001
  // Per-route fallback: register an alias path for the same component.
4100
4002
  // e.g. { path: '/docs/:section', fallback: '/docs', component: 'docs-page' }
4101
- // When matched via fallback, missing params are undefined → pages `default` kicks in.
4003
+ // When matched via fallback, missing params are undefined.
4102
4004
  if (route.fallback) {
4103
4005
  const fbKeys = [];
4104
4006
  const fbPattern = route.fallback
@@ -5362,8 +5264,8 @@ $.guardCallback = guardCallback;
5362
5264
  $.validate = validate;
5363
5265
 
5364
5266
  // --- Meta ------------------------------------------------------------------
5365
- $.version = '0.8.6';
5366
- $.libSize = '~91 KB';
5267
+ $.version = '0.8.8';
5268
+ $.libSize = '~89 KB';
5367
5269
  $.meta = {}; // populated at build time by CLI bundler
5368
5270
 
5369
5271
  $.noConflict = () => {