@emkodev/emroute 1.6.2 → 1.6.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.
Files changed (128) hide show
  1. package/README.md +6 -6
  2. package/dist/runtime/abstract.runtime.d.ts +94 -0
  3. package/dist/runtime/abstract.runtime.js +339 -0
  4. package/dist/runtime/abstract.runtime.js.map +1 -0
  5. package/dist/runtime/bun/esbuild-runtime-loader.plugin.d.ts +25 -0
  6. package/dist/runtime/bun/esbuild-runtime-loader.plugin.js +72 -0
  7. package/dist/runtime/bun/esbuild-runtime-loader.plugin.js.map +1 -0
  8. package/dist/runtime/bun/fs/bun-fs.runtime.d.ts +21 -0
  9. package/dist/runtime/bun/fs/bun-fs.runtime.js +205 -0
  10. package/dist/runtime/bun/fs/bun-fs.runtime.js.map +1 -0
  11. package/dist/runtime/bun/sqlite/bun-sqlite.runtime.d.ts +27 -0
  12. package/dist/runtime/bun/sqlite/bun-sqlite.runtime.js +234 -0
  13. package/dist/runtime/bun/sqlite/bun-sqlite.runtime.js.map +1 -0
  14. package/dist/runtime/sitemap.generator.d.ts +58 -0
  15. package/dist/runtime/sitemap.generator.js +107 -0
  16. package/dist/runtime/sitemap.generator.js.map +1 -0
  17. package/dist/runtime/universal/fs/universal-fs.runtime.d.ts +29 -0
  18. package/dist/runtime/universal/fs/universal-fs.runtime.js +213 -0
  19. package/dist/runtime/universal/fs/universal-fs.runtime.js.map +1 -0
  20. package/dist/server/codegen.util.d.ts +16 -0
  21. package/dist/server/codegen.util.js +46 -0
  22. package/dist/server/codegen.util.js.map +1 -0
  23. package/dist/server/emroute.server.d.ts +37 -0
  24. package/dist/server/emroute.server.js +314 -0
  25. package/dist/server/emroute.server.js.map +1 -0
  26. package/dist/server/esbuild-manifest.plugin.d.ts +26 -0
  27. package/dist/server/esbuild-manifest.plugin.js +187 -0
  28. package/dist/server/esbuild-manifest.plugin.js.map +1 -0
  29. package/dist/server/scanner.util.d.ts +22 -0
  30. package/dist/server/scanner.util.js +194 -0
  31. package/dist/server/scanner.util.js.map +1 -0
  32. package/dist/server/server-api.type.d.ts +71 -0
  33. package/dist/server/server-api.type.js +9 -0
  34. package/dist/server/server-api.type.js.map +1 -0
  35. package/dist/src/component/abstract.component.d.ts +197 -0
  36. package/dist/src/component/abstract.component.js +84 -0
  37. package/dist/src/component/abstract.component.js.map +1 -0
  38. package/dist/src/component/page.component.d.ts +74 -0
  39. package/dist/src/component/page.component.js +107 -0
  40. package/dist/src/component/page.component.js.map +1 -0
  41. package/dist/src/component/widget.component.d.ts +47 -0
  42. package/dist/src/component/widget.component.js +69 -0
  43. package/dist/src/component/widget.component.js.map +1 -0
  44. package/dist/src/element/component.element.d.ts +79 -0
  45. package/dist/src/element/component.element.js +293 -0
  46. package/dist/src/element/component.element.js.map +1 -0
  47. package/dist/src/element/markdown.element.d.ts +36 -0
  48. package/dist/src/element/markdown.element.js +93 -0
  49. package/dist/src/element/markdown.element.js.map +1 -0
  50. package/dist/src/element/slot.element.d.ts +30 -0
  51. package/dist/src/element/slot.element.js +31 -0
  52. package/dist/src/element/slot.element.js.map +1 -0
  53. package/dist/src/index.d.ts +23 -0
  54. package/dist/src/index.js +24 -0
  55. package/dist/src/index.js.map +1 -0
  56. package/dist/src/overlay/mod.d.ts +9 -0
  57. package/dist/src/overlay/mod.js +9 -0
  58. package/dist/src/overlay/mod.js.map +1 -0
  59. package/dist/src/overlay/overlay.css.d.ts +8 -0
  60. package/dist/src/overlay/overlay.css.js +170 -0
  61. package/dist/src/overlay/overlay.css.js.map +1 -0
  62. package/dist/src/overlay/overlay.service.d.ts +14 -0
  63. package/dist/src/overlay/overlay.service.js +307 -0
  64. package/dist/src/overlay/overlay.service.js.map +1 -0
  65. package/dist/src/overlay/overlay.type.d.ts +33 -0
  66. package/dist/src/overlay/overlay.type.js +11 -0
  67. package/dist/src/overlay/overlay.type.js.map +1 -0
  68. package/dist/src/renderer/spa/base.renderer.d.ts +39 -0
  69. package/dist/src/renderer/spa/base.renderer.js +149 -0
  70. package/dist/src/renderer/spa/base.renderer.js.map +1 -0
  71. package/dist/src/renderer/spa/hash.renderer.d.ts +78 -0
  72. package/dist/src/renderer/spa/hash.renderer.js +162 -0
  73. package/dist/src/renderer/spa/hash.renderer.js.map +1 -0
  74. package/dist/src/renderer/spa/html.renderer.d.ts +81 -0
  75. package/dist/src/renderer/spa/html.renderer.js +304 -0
  76. package/dist/src/renderer/spa/html.renderer.js.map +1 -0
  77. package/dist/src/renderer/spa/mod.d.ts +30 -0
  78. package/dist/src/renderer/spa/mod.js +35 -0
  79. package/dist/src/renderer/spa/mod.js.map +1 -0
  80. package/dist/src/renderer/ssr/html.renderer.d.ts +49 -0
  81. package/dist/src/renderer/ssr/html.renderer.js +108 -0
  82. package/dist/src/renderer/ssr/html.renderer.js.map +1 -0
  83. package/dist/src/renderer/ssr/md.renderer.d.ts +40 -0
  84. package/dist/src/renderer/ssr/md.renderer.js +100 -0
  85. package/dist/src/renderer/ssr/md.renderer.js.map +1 -0
  86. package/dist/src/renderer/ssr/ssr.renderer.d.ts +74 -0
  87. package/dist/src/renderer/ssr/ssr.renderer.js +185 -0
  88. package/dist/src/renderer/ssr/ssr.renderer.js.map +1 -0
  89. package/dist/src/route/route.core.d.ts +129 -0
  90. package/dist/src/route/route.core.js +255 -0
  91. package/dist/src/route/route.core.js.map +1 -0
  92. package/dist/src/route/route.matcher.d.ts +86 -0
  93. package/dist/src/route/route.matcher.js +214 -0
  94. package/dist/src/route/route.matcher.js.map +1 -0
  95. package/dist/src/type/logger.type.d.ts +17 -0
  96. package/dist/src/type/logger.type.js +9 -0
  97. package/dist/src/type/logger.type.js.map +1 -0
  98. package/dist/src/type/markdown.type.d.ts +20 -0
  99. package/dist/src/type/markdown.type.js +2 -0
  100. package/dist/src/type/markdown.type.js.map +1 -0
  101. package/dist/src/type/route.type.d.ts +112 -0
  102. package/dist/src/type/route.type.js +8 -0
  103. package/dist/src/type/route.type.js.map +1 -0
  104. package/dist/src/type/widget.type.d.ts +55 -0
  105. package/dist/src/type/widget.type.js +10 -0
  106. package/dist/src/type/widget.type.js.map +1 -0
  107. package/dist/src/util/html.util.d.ts +29 -0
  108. package/dist/src/util/html.util.js +158 -0
  109. package/dist/src/util/html.util.js.map +1 -0
  110. package/dist/src/util/logger.util.d.ts +26 -0
  111. package/dist/src/util/logger.util.js +80 -0
  112. package/dist/src/util/logger.util.js.map +1 -0
  113. package/dist/src/util/widget-resolve.util.d.ts +52 -0
  114. package/dist/src/util/widget-resolve.util.js +149 -0
  115. package/dist/src/util/widget-resolve.util.js.map +1 -0
  116. package/dist/src/widget/breadcrumb.widget.d.ts +48 -0
  117. package/dist/src/widget/breadcrumb.widget.js +72 -0
  118. package/dist/src/widget/breadcrumb.widget.js.map +1 -0
  119. package/dist/src/widget/page-title.widget.d.ts +33 -0
  120. package/dist/src/widget/page-title.widget.js +33 -0
  121. package/dist/src/widget/page-title.widget.js.map +1 -0
  122. package/dist/src/widget/widget.parser.d.ts +26 -0
  123. package/dist/src/widget/widget.parser.js +76 -0
  124. package/dist/src/widget/widget.parser.js.map +1 -0
  125. package/dist/src/widget/widget.registry.d.ts +23 -0
  126. package/dist/src/widget/widget.registry.js +42 -0
  127. package/dist/src/widget/widget.registry.js.map +1 -0
  128. package/package.json +63 -13
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Logger Utility
3
+ *
4
+ * Provides structured logging for emroute internals.
5
+ * Enable via localStorage: localStorage.setItem('emroute:debug', 'true')
6
+ */
7
+ export declare const logger: {
8
+ /** Enable debug logging (persists in localStorage) */
9
+ enable(): void;
10
+ /** Disable debug logging */
11
+ disable(): void;
12
+ /** Log general information */
13
+ info(category: string, message: string, data?: unknown): void;
14
+ /** Log navigation events */
15
+ nav(action: string, from: string, to: string, data?: Record<string, unknown>): void;
16
+ /** Log rendering events */
17
+ render(component: string, route: string, mode?: string): void;
18
+ /** Log link interception */
19
+ link(action: "intercept" | "passthrough", href: string, reason?: string): void;
20
+ /** Log widget lifecycle */
21
+ widget(event: string, name: string, data?: unknown): void;
22
+ /** Log warnings (always shown, not gated by debug flag) */
23
+ warn(message: string): void;
24
+ /** Log SSR adoption */
25
+ ssr(action: string, route: string): void;
26
+ };
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Logger Utility
3
+ *
4
+ * Provides structured logging for emroute internals.
5
+ * Enable via localStorage: localStorage.setItem('emroute:debug', 'true')
6
+ */
7
+ const STORAGE_KEY = 'emroute:debug';
8
+ const PREFIX = '[emroute]';
9
+ function isEnabled() {
10
+ if (typeof globalThis.localStorage === 'undefined')
11
+ return false;
12
+ return globalThis.localStorage.getItem(STORAGE_KEY) === 'true';
13
+ }
14
+ export const logger = {
15
+ /** Enable debug logging (persists in localStorage) */
16
+ enable() {
17
+ if (typeof globalThis.localStorage !== 'undefined') {
18
+ globalThis.localStorage.setItem(STORAGE_KEY, 'true');
19
+ console.log(`${PREFIX} Debug logging enabled`);
20
+ }
21
+ },
22
+ /** Disable debug logging */
23
+ disable() {
24
+ if (typeof globalThis.localStorage !== 'undefined') {
25
+ globalThis.localStorage.removeItem(STORAGE_KEY);
26
+ console.log(`${PREFIX} Debug logging disabled`);
27
+ }
28
+ },
29
+ /** Log general information */
30
+ info(category, message, data) {
31
+ if (!isEnabled())
32
+ return;
33
+ const prefix = `${PREFIX} [${category}]`;
34
+ if (data !== undefined) {
35
+ console.log(prefix, message, data);
36
+ }
37
+ else {
38
+ console.log(prefix, message);
39
+ }
40
+ },
41
+ /** Log navigation events */
42
+ nav(action, from, to, data) {
43
+ if (!isEnabled())
44
+ return;
45
+ console.log(`${PREFIX} [nav] ${action}:`, { from, to, ...(data ?? {}) });
46
+ },
47
+ /** Log rendering events */
48
+ render(component, route, mode) {
49
+ if (!isEnabled())
50
+ return;
51
+ const modeStr = mode ? ` [mode=${mode}]` : '';
52
+ console.log(`${PREFIX} [render]${modeStr} ${component} → ${route}`);
53
+ },
54
+ /** Log link interception */
55
+ link(action, href, reason) {
56
+ if (!isEnabled())
57
+ return;
58
+ const reasonStr = reason ? ` (${reason})` : '';
59
+ console.log(`${PREFIX} [link] ${action}: ${href}${reasonStr}`);
60
+ },
61
+ /** Log widget lifecycle */
62
+ widget(event, name, data) {
63
+ if (!isEnabled())
64
+ return;
65
+ console.log(`${PREFIX} [widget] ${event}: ${name}`, data ?? '');
66
+ },
67
+ /** Log warnings (always shown, not gated by debug flag) */
68
+ warn(message) {
69
+ console.warn(`${PREFIX}`, message);
70
+ },
71
+ /** Log SSR adoption */
72
+ ssr(action, route) {
73
+ if (!isEnabled())
74
+ return;
75
+ console.log(`${PREFIX} [ssr] ${action}: ${route}`);
76
+ },
77
+ };
78
+ // Expose globally for console access
79
+ globalThis.__emroute_logger = logger;
80
+ //# sourceMappingURL=logger.util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.util.js","sourceRoot":"","sources":["../../../src/util/logger.util.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Widget Resolve Utilities
3
+ *
4
+ * Server-side resolution of <widget-*> tags in HTML.
5
+ * Calls getData() + renderHTML() on widgets and injects SSR hydration data.
6
+ */
7
+ import type { Component, ContextProvider } from '../component/abstract.component.ts';
8
+ import type { RouteInfo } from '../type/route.type.ts';
9
+ /** Maximum nesting depth for widgets to prevent infinite loops */
10
+ export declare const MAX_WIDGET_DEPTH = 10;
11
+ /**
12
+ * Recursively resolve widgets in content with depth limit.
13
+ *
14
+ * Generic utility used by both HTML and Markdown widget resolution.
15
+ * Each depth level processes all widgets concurrently, then recurses
16
+ * into each rendered result to resolve nested widgets.
17
+ *
18
+ * @param content - Content containing widgets
19
+ * @param parse - Find widgets in content
20
+ * @param resolve - Resolve a single widget to rendered output
21
+ * @param replace - Replace widgets with resolved content
22
+ * @param depth - Current recursion depth (internal)
23
+ * @returns Content with all widgets recursively resolved
24
+ */
25
+ export declare function resolveRecursively<T>(content: string, parse: (content: string) => T[], resolve: (widget: T) => Promise<string>, replace: (content: string, replacements: Map<T, string>) => string, depth?: number): Promise<string>;
26
+ /**
27
+ * Resolve <widget-*> tags in HTML by calling getData() + renderHTML()
28
+ * via the widget registry. Injects rendered content and boolean ssr attribute.
29
+ *
30
+ * Supports nested widgets: if a widget's renderHTML() returns HTML containing
31
+ * other <widget-*> tags, those will be resolved recursively up to MAX_WIDGET_DEPTH.
32
+ *
33
+ * Before: <widget-crypto-price coin="bitcoin"></widget-crypto-price>
34
+ * After: <widget-crypto-price coin="bitcoin" ssr><template shadowrootmode="open"><span>$42,000</span></template></widget-crypto-price>
35
+ *
36
+ * When a widget has `exposeSsrData = true`, the getData() result is serialized
37
+ * as JSON text in the light DOM (alongside the shadow root template):
38
+ * After: <widget-crypto-price coin="bitcoin" ssr><template shadowrootmode="open"><span>$42,000</span></template>{"price":42000}</widget-crypto-price>
39
+ */
40
+ export declare function resolveWidgetTags(html: string, registry: {
41
+ get(name: string): Component | undefined;
42
+ }, routeInfo: RouteInfo, loadFiles?: (widgetName: string, declaredFiles?: {
43
+ html?: string;
44
+ md?: string;
45
+ css?: string;
46
+ }) => Promise<{
47
+ html?: string;
48
+ md?: string;
49
+ css?: string;
50
+ }>, contextProvider?: ContextProvider): Promise<string>;
51
+ /** Parse HTML attribute string into params object (kebab→camelCase, JSON.parse with string fallback). */
52
+ export declare function parseAttrsToParams(attrsString: string): Record<string, unknown>;
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Widget Resolve Utilities
3
+ *
4
+ * Server-side resolution of <widget-*> tags in HTML.
5
+ * Calls getData() + renderHTML() on widgets and injects SSR hydration data.
6
+ */
7
+ import { logger } from "../type/logger.type.js";
8
+ import { LAZY_ATTR, SSR_ATTR } from "./html.util.js";
9
+ /** Maximum nesting depth for widgets to prevent infinite loops */
10
+ export const MAX_WIDGET_DEPTH = 10;
11
+ /**
12
+ * Recursively resolve widgets in content with depth limit.
13
+ *
14
+ * Generic utility used by both HTML and Markdown widget resolution.
15
+ * Each depth level processes all widgets concurrently, then recurses
16
+ * into each rendered result to resolve nested widgets.
17
+ *
18
+ * @param content - Content containing widgets
19
+ * @param parse - Find widgets in content
20
+ * @param resolve - Resolve a single widget to rendered output
21
+ * @param replace - Replace widgets with resolved content
22
+ * @param depth - Current recursion depth (internal)
23
+ * @returns Content with all widgets recursively resolved
24
+ */
25
+ export async function resolveRecursively(content, parse, resolve, replace, depth = 0) {
26
+ if (depth >= MAX_WIDGET_DEPTH) {
27
+ logger.warn(`Widget nesting depth limit reached (${MAX_WIDGET_DEPTH}). ` +
28
+ 'Possible circular dependency or excessive nesting.');
29
+ return content;
30
+ }
31
+ const widgets = parse(content);
32
+ if (widgets.length === 0)
33
+ return content;
34
+ const replacements = new Map();
35
+ await Promise.all(widgets.map(async (widget) => {
36
+ let rendered = await resolve(widget);
37
+ // Recursively resolve any nested widgets in the rendered output
38
+ rendered = await resolveRecursively(rendered, parse, resolve, replace, depth + 1);
39
+ replacements.set(widget, rendered);
40
+ }));
41
+ return replace(content, replacements);
42
+ }
43
+ /**
44
+ * Resolve <widget-*> tags in HTML by calling getData() + renderHTML()
45
+ * via the widget registry. Injects rendered content and boolean ssr attribute.
46
+ *
47
+ * Supports nested widgets: if a widget's renderHTML() returns HTML containing
48
+ * other <widget-*> tags, those will be resolved recursively up to MAX_WIDGET_DEPTH.
49
+ *
50
+ * Before: <widget-crypto-price coin="bitcoin"></widget-crypto-price>
51
+ * After: <widget-crypto-price coin="bitcoin" ssr><template shadowrootmode="open"><span>$42,000</span></template></widget-crypto-price>
52
+ *
53
+ * When a widget has `exposeSsrData = true`, the getData() result is serialized
54
+ * as JSON text in the light DOM (alongside the shadow root template):
55
+ * After: <widget-crypto-price coin="bitcoin" ssr><template shadowrootmode="open"><span>$42,000</span></template>{"price":42000}</widget-crypto-price>
56
+ */
57
+ export function resolveWidgetTags(html, registry, routeInfo, loadFiles, contextProvider) {
58
+ const tagPattern = /<widget-(?<name>[a-z][a-z0-9-]*)(?<attrs>\s[^>]*)?>(?<content>.*?)<\/widget-\k<name>>/gis;
59
+ // Wrapping info stored per-match so replace() can apply it after recursion
60
+ const wrappers = new Map();
61
+ // Matches standalone ssr attribute (boolean or with value), not as substring of another value
62
+ const ssrAttrPattern = new RegExp(`\\s${SSR_ATTR}(?:\\s|=|$)`);
63
+ // Parse: find unprocessed widget tags
64
+ const parse = (content) => {
65
+ const matches = content.matchAll(tagPattern).toArray();
66
+ return matches.filter((match) => {
67
+ const attrsString = match.groups.attrs || '';
68
+ return !ssrAttrPattern.test(attrsString);
69
+ });
70
+ };
71
+ // Resolve: render a single widget's inner content (no outer tag wrapping — that's in replace)
72
+ const resolve = async (match) => {
73
+ const widgetName = match.groups.name;
74
+ const attrsString = match.groups.attrs?.trim() ?? '';
75
+ const widget = registry.get(widgetName);
76
+ if (!widget)
77
+ return match[0]; // no widget found — leave original tag as-is
78
+ const params = parseAttrsToParams(attrsString);
79
+ try {
80
+ let files;
81
+ if (loadFiles) {
82
+ files = await loadFiles(widgetName, widget.files);
83
+ }
84
+ const baseContext = { ...routeInfo, files };
85
+ const context = contextProvider ? contextProvider(baseContext) : baseContext;
86
+ const data = await widget.getData({ params, context });
87
+ const rendered = widget.renderHTML({ data, params, context });
88
+ // Store wrapping info — applied in replace() after recursion resolves nested widgets
89
+ wrappers.set(match, {
90
+ tagName: `widget-${widgetName}`,
91
+ attrs: attrsString ? ` ${attrsString}` : '',
92
+ ssrData: widget.exposeSsrData ? escapeAttr(JSON.stringify(data)) : '',
93
+ });
94
+ return rendered;
95
+ }
96
+ catch (e) {
97
+ logger.error(`[SSR HTML] Widget "${widgetName}" render failed`, e instanceof Error ? e : undefined);
98
+ return match[0]; // render failed — leave original tag as-is
99
+ }
100
+ };
101
+ // Replace: wrap resolved content in outer tag + DSD template, then substitute by index
102
+ const replace = (content, replacements) => {
103
+ let result = content;
104
+ const entries = [...replacements.entries()].sort((a, b) => b[0].index - a[0].index);
105
+ for (const [match, innerHtml] of entries) {
106
+ const start = match.index;
107
+ const end = start + match[0].length;
108
+ const wrap = wrappers.get(match);
109
+ const lightDomData = wrap?.ssrData ? wrap.ssrData : '';
110
+ const replacement = wrap
111
+ ? `<${wrap.tagName}${wrap.attrs} ${SSR_ATTR}><template shadowrootmode="open">${innerHtml}</template>${lightDomData}</${wrap.tagName}>`
112
+ : innerHtml; // no wrapper = unresolved widget, innerHtml is the original tag
113
+ result = result.slice(0, start) + replacement + result.slice(end);
114
+ }
115
+ return result;
116
+ };
117
+ return resolveRecursively(html, parse, resolve, replace);
118
+ }
119
+ /** Parse HTML attribute string into params object (kebab→camelCase, JSON.parse with string fallback). */
120
+ export function parseAttrsToParams(attrsString) {
121
+ const params = {};
122
+ if (!attrsString)
123
+ return params;
124
+ const attrPattern = /(?<attr>[a-z][a-z0-9-]*)(?:="(?<dq>[^"]*)"|='(?<sq>[^']*)'|=(?<uq>[^\s>]+))?/gi;
125
+ for (const match of attrsString.matchAll(attrPattern)) {
126
+ const { attr: attrName, dq, sq, uq } = match.groups;
127
+ if (attrName === SSR_ATTR || attrName === LAZY_ATTR)
128
+ continue;
129
+ const key = attrName.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
130
+ const rawValue = dq ?? sq ?? uq;
131
+ if (rawValue === undefined) {
132
+ params[key] = '';
133
+ continue;
134
+ }
135
+ const raw = rawValue.replaceAll('&amp;', '&').replaceAll('&#39;', "'").replaceAll('&quot;', '"');
136
+ try {
137
+ params[key] = JSON.parse(raw);
138
+ }
139
+ catch {
140
+ params[key] = raw;
141
+ }
142
+ }
143
+ return params;
144
+ }
145
+ /** Escape a value for use in a single-quoted HTML attribute. */
146
+ function escapeAttr(value) {
147
+ return value.replaceAll('&', '&amp;').replaceAll("'", '&#39;');
148
+ }
149
+ //# sourceMappingURL=widget-resolve.util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget-resolve.util.js","sourceRoot":"","sources":["../../../src/util/widget-resolve.util.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Built-in Breadcrumb Widget
3
+ *
4
+ * Renders breadcrumb navigation from the current URL path.
5
+ * Uses /html/ prefix for links (content-first routing convention).
6
+ *
7
+ * Usage in .page.html:
8
+ * <widget-breadcrumb></widget-breadcrumb>
9
+ * <widget-breadcrumb separator=" / " class="my-breadcrumbs"></widget-breadcrumb>
10
+ *
11
+ * Usage in .page.md:
12
+ * ```widget:breadcrumb
13
+ * {}
14
+ * ```
15
+ */
16
+ import { WidgetComponent } from '../component/widget.component.ts';
17
+ import type { ComponentContext } from '../component/abstract.component.ts';
18
+ interface BreadcrumbParams {
19
+ separator?: string;
20
+ class?: string;
21
+ }
22
+ interface BreadcrumbSegment {
23
+ label: string;
24
+ href: string;
25
+ }
26
+ interface BreadcrumbData {
27
+ segments: BreadcrumbSegment[];
28
+ }
29
+ export declare class BreadcrumbWidget extends WidgetComponent<BreadcrumbParams, BreadcrumbData> {
30
+ readonly name = "breadcrumb";
31
+ getData(args: {
32
+ params: BreadcrumbParams;
33
+ signal?: AbortSignal;
34
+ context: ComponentContext;
35
+ }): Promise<BreadcrumbData | null>;
36
+ private resolvePathname;
37
+ renderHTML(args: {
38
+ data: BreadcrumbData | null;
39
+ params: BreadcrumbParams;
40
+ context: ComponentContext;
41
+ }): string;
42
+ renderMarkdown(args: {
43
+ data: BreadcrumbData | null;
44
+ params: BreadcrumbParams;
45
+ context: ComponentContext;
46
+ }): string;
47
+ }
48
+ export {};
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Built-in Breadcrumb Widget
3
+ *
4
+ * Renders breadcrumb navigation from the current URL path.
5
+ * Uses /html/ prefix for links (content-first routing convention).
6
+ *
7
+ * Usage in .page.html:
8
+ * <widget-breadcrumb></widget-breadcrumb>
9
+ * <widget-breadcrumb separator=" / " class="my-breadcrumbs"></widget-breadcrumb>
10
+ *
11
+ * Usage in .page.md:
12
+ * ```widget:breadcrumb
13
+ * {}
14
+ * ```
15
+ */
16
+ import { WidgetComponent } from "../component/widget.component.js";
17
+ import { escapeHtml } from "../util/html.util.js";
18
+ import { DEFAULT_BASE_PATH } from "../route/route.core.js";
19
+ const DEFAULT_HTML_SEPARATOR = ' \u203A ';
20
+ const DEFAULT_MD_SEPARATOR = ' > ';
21
+ export class BreadcrumbWidget extends WidgetComponent {
22
+ name = 'breadcrumb';
23
+ getData(args) {
24
+ const htmlBase = args.context.basePath ?? DEFAULT_BASE_PATH.html;
25
+ const pathname = args.context.pathname ?? this.resolvePathname(htmlBase);
26
+ // Skip basePath segments for display — only show route segments
27
+ const barePathname = htmlBase && pathname.startsWith(htmlBase)
28
+ ? pathname.slice(htmlBase.length) || '/'
29
+ : pathname;
30
+ const parts = barePathname.split('/').filter(Boolean);
31
+ const segments = [
32
+ { label: 'Home', href: htmlBase + '/' },
33
+ ];
34
+ let accumulated = htmlBase;
35
+ for (const part of parts) {
36
+ accumulated += '/' + part;
37
+ segments.push({
38
+ label: part.charAt(0).toUpperCase() + part.slice(1).replace(/-/g, ' '),
39
+ href: accumulated,
40
+ });
41
+ }
42
+ return Promise.resolve({ segments });
43
+ }
44
+ resolvePathname(htmlBase) {
45
+ if (typeof globalThis.location === 'undefined')
46
+ return htmlBase + '/';
47
+ return location.pathname;
48
+ }
49
+ renderHTML(args) {
50
+ if (!args.data || args.data.segments.length === 0)
51
+ return '';
52
+ const sep = args.params.separator ?? DEFAULT_HTML_SEPARATOR;
53
+ const segments = args.data.segments;
54
+ const items = segments.map((seg, i) => {
55
+ const escaped = escapeHtml(seg.label);
56
+ if (i === segments.length - 1) {
57
+ return `<span aria-current="page">${escaped}</span>`;
58
+ }
59
+ return `<a href="${escapeHtml(seg.href)}">${escaped}</a>`;
60
+ });
61
+ return `<nav aria-label="Breadcrumb">${items.join(escapeHtml(sep))}</nav>`;
62
+ }
63
+ renderMarkdown(args) {
64
+ if (!args.data || args.data.segments.length === 0)
65
+ return '';
66
+ const sep = args.params.separator ?? DEFAULT_MD_SEPARATOR;
67
+ return args.data.segments
68
+ .map((seg, i, arr) => i === arr.length - 1 ? `**${seg.label}**` : `[${seg.label}](${seg.href})`)
69
+ .join(sep);
70
+ }
71
+ }
72
+ //# sourceMappingURL=breadcrumb.widget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"breadcrumb.widget.js","sourceRoot":"","sources":["../../../src/widget/breadcrumb.widget.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Built-in Page Title Widget
3
+ *
4
+ * Sets document.title from .page.html and .page.md files without needing
5
+ * a .page.ts component. Renders no visible output.
6
+ *
7
+ * Usage in .page.html:
8
+ * <widget-page-title title="About Us"></widget-page-title>
9
+ */
10
+ import { WidgetComponent } from '../component/widget.component.ts';
11
+ interface PageTitleParams {
12
+ title: string;
13
+ }
14
+ interface PageTitleData {
15
+ title: string;
16
+ }
17
+ export declare class PageTitleWidget extends WidgetComponent<PageTitleParams, PageTitleData> {
18
+ readonly name = "page-title";
19
+ getData(args: {
20
+ params: PageTitleParams;
21
+ signal?: AbortSignal;
22
+ }): Promise<PageTitleData | null>;
23
+ renderHTML(args: {
24
+ data: PageTitleData | null;
25
+ params: PageTitleParams;
26
+ }): string;
27
+ renderMarkdown(_args: {
28
+ data: PageTitleData | null;
29
+ params: PageTitleParams;
30
+ }): string;
31
+ validateParams(params: PageTitleParams): string | undefined;
32
+ }
33
+ export {};
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Built-in Page Title Widget
3
+ *
4
+ * Sets document.title from .page.html and .page.md files without needing
5
+ * a .page.ts component. Renders no visible output.
6
+ *
7
+ * Usage in .page.html:
8
+ * <widget-page-title title="About Us"></widget-page-title>
9
+ */
10
+ import { WidgetComponent } from "../component/widget.component.js";
11
+ export class PageTitleWidget extends WidgetComponent {
12
+ name = 'page-title';
13
+ getData(args) {
14
+ return Promise.resolve({ title: args.params.title });
15
+ }
16
+ renderHTML(args) {
17
+ const title = args.data?.title ?? args.params.title;
18
+ if (title && typeof document !== 'undefined') {
19
+ document.title = title;
20
+ }
21
+ return '';
22
+ }
23
+ renderMarkdown(_args) {
24
+ return '';
25
+ }
26
+ validateParams(params) {
27
+ if (!params.title || typeof params.title !== 'string') {
28
+ return 'page-title widget requires a "title" string param';
29
+ }
30
+ return undefined;
31
+ }
32
+ }
33
+ //# sourceMappingURL=page-title.widget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-title.widget.js","sourceRoot":"","sources":["../../../src/widget/page-title.widget.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAUnE,MAAM,OAAO,eAAgB,SAAQ,eAA+C;IAChE,IAAI,GAAG,YAAY,CAAC;IAE7B,OAAO,CACd,IAAuD;QAEvD,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;IACvD,CAAC;IAEQ,UAAU,CACjB,IAA6D;QAE7D,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;QACpD,IAAI,KAAK,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;YAC7C,QAAQ,CAAC,KAAK,GAAG,KAAK,CAAC;QACzB,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;IAEQ,cAAc,CACrB,KAA8D;QAE9D,OAAO,EAAE,CAAC;IACZ,CAAC;IAEQ,cAAc,CAAC,MAAuB;QAC7C,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YACtD,OAAO,mDAAmD,CAAC;QAC7D,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;CACF"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Widget Parser
3
+ *
4
+ * Parses fenced widget blocks from markdown content.
5
+ *
6
+ * Syntax:
7
+ * ```widget:widget-name
8
+ * {"key": "value"}
9
+ * ```
10
+ */
11
+ import type { ParsedWidgetBlock } from '../type/widget.type.ts';
12
+ /**
13
+ * Parse all widget blocks from markdown content.
14
+ *
15
+ * @param markdown - Markdown content to parse
16
+ * @returns Array of parsed widget blocks with positions
17
+ */
18
+ export declare function parseWidgetBlocks(markdown: string): ParsedWidgetBlock[];
19
+ /**
20
+ * Replace widget blocks in markdown with rendered content.
21
+ *
22
+ * @param markdown - Original markdown content
23
+ * @param replacements - Map of parsed blocks to replacement strings
24
+ * @returns Markdown with widget blocks replaced
25
+ */
26
+ export declare function replaceWidgetBlocks(markdown: string, replacements: Map<ParsedWidgetBlock, string>): string;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Widget Parser
3
+ *
4
+ * Parses fenced widget blocks from markdown content.
5
+ *
6
+ * Syntax:
7
+ * ```widget:widget-name
8
+ * {"key": "value"}
9
+ * ```
10
+ */
11
+ /**
12
+ * Pattern to match widget fenced code blocks.
13
+ * Captures: widget name, params content
14
+ */
15
+ const WIDGET_PATTERN = /```widget:(?<name>[a-z][a-z0-9-]*)\n(?<params>.*?)```/gs;
16
+ /**
17
+ * Parse all widget blocks from markdown content.
18
+ *
19
+ * @param markdown - Markdown content to parse
20
+ * @returns Array of parsed widget blocks with positions
21
+ */
22
+ export function parseWidgetBlocks(markdown) {
23
+ const blocks = [];
24
+ for (const match of markdown.matchAll(WIDGET_PATTERN)) {
25
+ const fullMatch = match[0];
26
+ const { name: widgetName, params: paramsRaw } = match.groups;
27
+ const paramsJson = paramsRaw.trim();
28
+ const startIndex = match.index;
29
+ const block = {
30
+ fullMatch,
31
+ widgetName,
32
+ params: null,
33
+ startIndex,
34
+ endIndex: startIndex + fullMatch.length,
35
+ };
36
+ // Parse JSON params if present
37
+ if (paramsJson) {
38
+ try {
39
+ const parsed = JSON.parse(paramsJson);
40
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
41
+ block.params = parsed;
42
+ }
43
+ else {
44
+ block.parseError = 'Params must be a JSON object';
45
+ }
46
+ }
47
+ catch (e) {
48
+ block.parseError = `Invalid JSON: ${e instanceof Error ? e.message : String(e)}`;
49
+ }
50
+ }
51
+ else {
52
+ // Empty params is valid - use empty object
53
+ block.params = {};
54
+ }
55
+ blocks.push(block);
56
+ }
57
+ return blocks;
58
+ }
59
+ /**
60
+ * Replace widget blocks in markdown with rendered content.
61
+ *
62
+ * @param markdown - Original markdown content
63
+ * @param replacements - Map of parsed blocks to replacement strings
64
+ * @returns Markdown with widget blocks replaced
65
+ */
66
+ export function replaceWidgetBlocks(markdown, replacements) {
67
+ // Sort blocks by position descending to replace from end first
68
+ // This preserves indices during replacement
69
+ const sortedBlocks = [...replacements.entries()].sort(([a], [b]) => b.startIndex - a.startIndex);
70
+ let result = markdown;
71
+ for (const [block, replacement] of sortedBlocks) {
72
+ result = result.slice(0, block.startIndex) + replacement + result.slice(block.endIndex);
73
+ }
74
+ return result;
75
+ }
76
+ //# sourceMappingURL=widget.parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget.parser.js","sourceRoot":"","sources":["../../../src/widget/widget.parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH;;;GAGG;AACH,MAAM,cAAc,GAAG,yDAAyD,CAAC;AAEjF;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,MAAM,MAAM,GAAwB,EAAE,CAAC;IAEvC,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;QACtD,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAC3B,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC,MAAO,CAAC;QAC9D,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC;QACpC,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC;QAE/B,MAAM,KAAK,GAAsB;YAC/B,SAAS;YACT,UAAU;YACV,MAAM,EAAE,IAAI;YACZ,UAAU;YACV,QAAQ,EAAE,UAAU,GAAG,SAAS,CAAC,MAAM;SACxC,CAAC;QAEF,+BAA+B;QAC/B,IAAI,UAAU,EAAE,CAAC;YACf,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;gBACtC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC5E,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;gBACxB,CAAC;qBAAM,CAAC;oBACN,KAAK,CAAC,UAAU,GAAG,8BAA8B,CAAC;gBACpD,CAAC;YACH,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,KAAK,CAAC,UAAU,GAAG,iBAAiB,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;YACnF,CAAC;QACH,CAAC;aAAM,CAAC;YACN,2CAA2C;YAC3C,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC;QACpB,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAAgB,EAChB,YAA4C;IAE5C,+DAA+D;IAC/D,4CAA4C;IAC5C,MAAM,YAAY,GAAG,CAAC,GAAG,YAAY,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CACnD,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,CAC1C,CAAC;IAEF,IAAI,MAAM,GAAG,QAAQ,CAAC;IACtB,KAAK,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,IAAI,YAAY,EAAE,CAAC;QAChD,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,GAAG,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC1F,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Widget Registry
3
+ *
4
+ * Canonical registry where all widgets live. Used by all renderers:
5
+ * - SSR HTML: resolves widgets by name, calls getData() + renderHTML()
6
+ * - SSR Markdown: resolves widgets by name, calls getData() + renderMarkdown()
7
+ * - SPA: registers custom elements for each widget
8
+ *
9
+ * Pages are NOT in this registry — they live in the routes manifest.
10
+ */
11
+ import type { WidgetComponent } from '../component/widget.component.ts';
12
+ import type { WidgetsManifest } from '../type/widget.type.ts';
13
+ export declare class WidgetRegistry {
14
+ private widgets;
15
+ /** Register a widget by its name. */
16
+ add(widget: WidgetComponent): void;
17
+ /** Look up a widget by name. */
18
+ get(name: string): WidgetComponent | undefined;
19
+ /** Iterate all registered widgets. */
20
+ [Symbol.iterator](): IterableIterator<WidgetComponent>;
21
+ /** Emit a WidgetsManifest from registered widgets. */
22
+ toManifest(): WidgetsManifest;
23
+ }