application.ts 1.0.0

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.
@@ -0,0 +1,264 @@
1
+ import { TemplateBinder } from 'template.ts';
2
+ import { App } from './App';
3
+ /**
4
+ * Abstract base class for creating views with Template.Ts
5
+ * Extends HTMLElement as a Web Component for seamless integration with StackView.Ts
6
+ * Custom elements are automatically registered when views are registered with App
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const template: `
11
+ * <div>
12
+ * <h1>Count: {{ count }}</h1>
13
+ * <button @on:click="increment">Increment</button>
14
+ * </div>`;
15
+ *
16
+ * class State {
17
+ * count: number = 0;
18
+ * increment: () => { this.count += 1; };
19
+ * }
20
+ *
21
+ * @Register
22
+ * class HomeView extends AppView<{ count: number }> {
23
+ * template(): string {
24
+ * return template;
25
+ * }
26
+ *
27
+ * state() {
28
+ * return new State();
29
+ * }
30
+ * }
31
+ *
32
+ * // Register with app - automatically registers as <home-view>
33
+ * app.registerView('HomeView', HomeView);
34
+ * ```
35
+ */
36
+ export class AppView extends HTMLElement {
37
+ constructor(options) {
38
+ super();
39
+ this.binder = null;
40
+ this._state = null;
41
+ this._isInitialized = false;
42
+ this._options = {
43
+ transitionClass: 'transition-fade',
44
+ autoUpdate: true,
45
+ useShadowDOM: false,
46
+ ...options
47
+ };
48
+ // Create shadow root if enabled
49
+ if (this._options.useShadowDOM) {
50
+ const shadow = this.attachShadow({ mode: 'open' });
51
+ shadow.innerHTML = this.template();
52
+ if (!shadow.firstElementChild || !(shadow.firstElementChild instanceof HTMLElement)) {
53
+ throw new Error('AppView template must have a single root element');
54
+ }
55
+ this._root = shadow.firstElementChild;
56
+ }
57
+ else {
58
+ this.innerHTML = this.template();
59
+ if (!this.firstElementChild || !(this.firstElementChild instanceof HTMLElement)) {
60
+ throw new Error('AppView template must have a single root element');
61
+ }
62
+ this._root = this.firstElementChild;
63
+ }
64
+ // Eager initialization in constructor
65
+ this.initialize();
66
+ }
67
+ /**
68
+ * Render the view with route parameters
69
+ * Initializes the template and binds state
70
+ * @param params - Route parameters from the router
71
+ */
72
+ initialize() {
73
+ if (this._isInitialized) {
74
+ return;
75
+ }
76
+ // Add app-view class
77
+ this.classList.add('app-view');
78
+ // Initialize state
79
+ this._state = this.state();
80
+ // Bind template using container
81
+ this.binder = new TemplateBinder(this._root, this._state, this._options.transitionClass);
82
+ this.binder.autoUpdate = this._options.autoUpdate ?? true;
83
+ this.binder.bind();
84
+ this._isInitialized = true;
85
+ }
86
+ /**
87
+ * Get the custom element tag name for this class
88
+ * Converts class name to kebab-case (e.g., HomeView -> home-view)
89
+ */
90
+ static getTagName() {
91
+ return this.name
92
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
93
+ .toLowerCase();
94
+ }
95
+ /**
96
+ * Register this class as a custom element
97
+ * Should be called before instantiation
98
+ */
99
+ static register() {
100
+ const tagName = this.getTagName();
101
+ if (!customElements.get(tagName)) {
102
+ customElements.define(tagName, this);
103
+ }
104
+ }
105
+ /**
106
+ * Get the current state
107
+ */
108
+ get viewState() {
109
+ if (!this._state) {
110
+ throw new Error('State not initialized. Call render() first.');
111
+ }
112
+ return this._state;
113
+ }
114
+ /**
115
+ * Get the route parameters passed to this view
116
+ */
117
+ get params() {
118
+ return this.app?.router?.currentParams || {};
119
+ }
120
+ /**
121
+ * Get the App instance by traversing up the DOM tree
122
+ */
123
+ get app() {
124
+ return App.fromElement(this);
125
+ }
126
+ /**
127
+ * Navigate to a specific path using the app's router
128
+ * @param path - The path to navigate to
129
+ */
130
+ navigate(path) {
131
+ const appInstance = this.app;
132
+ if (appInstance) {
133
+ appInstance.navigate(path);
134
+ }
135
+ else {
136
+ console.warn('Cannot navigate: App instance not found');
137
+ }
138
+ }
139
+ /**
140
+ * Update a single state value
141
+ * @param key - The state key to update
142
+ * @param value - The new value
143
+ */
144
+ setState(key, value) {
145
+ if (!this._state) {
146
+ console.warn('Cannot set state before initialization');
147
+ return;
148
+ }
149
+ this._state[key] = value;
150
+ // Call lifecycle hook
151
+ if (this.onStateChanged) {
152
+ this.onStateChanged(key, value);
153
+ }
154
+ // Auto-update if enabled
155
+ if (this._options.autoUpdate && this.binder) {
156
+ this.update();
157
+ }
158
+ }
159
+ /**
160
+ * Update multiple state values at once
161
+ * @param updates - Object with state updates
162
+ */
163
+ setStates(updates) {
164
+ if (!this._state) {
165
+ console.warn('Cannot set state before initialization');
166
+ return;
167
+ }
168
+ Object.assign(this._state, updates);
169
+ // Call lifecycle hooks for each update
170
+ if (this.onStateChanged) {
171
+ for (const [key, value] of Object.entries(updates)) {
172
+ this.onStateChanged(key, value);
173
+ }
174
+ }
175
+ // Auto-update if enabled
176
+ if (this._options.autoUpdate && this.binder) {
177
+ this.update();
178
+ }
179
+ }
180
+ /**
181
+ * Manually trigger a view update
182
+ * @param withAnimation - Whether to apply transition animation
183
+ */
184
+ update(withAnimation = true) {
185
+ if (this.binder) {
186
+ this.binder.update(withAnimation);
187
+ }
188
+ }
189
+ /**
190
+ * Update route parameters and re-trigger initialization logic
191
+ * Used when navigating to the same view with different parameters
192
+ * @param params - New route parameters
193
+ */
194
+ async updateParams(params = {}) {
195
+ // Call the parameter changed hook with old and new params
196
+ if (this.onParamsChanged) {
197
+ await this.onParamsChanged(params, this.params);
198
+ }
199
+ // Update the view if needed
200
+ if (this.binder && this._options.autoUpdate) {
201
+ this.update();
202
+ }
203
+ }
204
+ /**
205
+ * StackView lifecycle: called when view is about to be shown
206
+ */
207
+ async stackViewShowing() {
208
+ // Call before mount hook
209
+ if (this.onBeforeMount) {
210
+ await this.onBeforeMount();
211
+ }
212
+ }
213
+ /**
214
+ * StackView lifecycle: called when view is about to be hidden
215
+ */
216
+ async stackViewHiding() {
217
+ if (this.onBeforeUnmount) {
218
+ await this.onBeforeUnmount();
219
+ }
220
+ }
221
+ /**
222
+ * StackView lifecycle: called after view is hidden
223
+ */
224
+ async stackViewHidden() {
225
+ // Destroy binder
226
+ if (this.binder) {
227
+ this.binder.destroy();
228
+ this.binder = null;
229
+ }
230
+ this._isInitialized = false;
231
+ if (this.onUnmounted) {
232
+ await this.onUnmounted();
233
+ }
234
+ }
235
+ /**
236
+ * Web Component lifecycle: called when connected to DOM
237
+ */
238
+ async connectedCallback() {
239
+ // Initialize if not already done
240
+ if (!this._isInitialized) {
241
+ await this.initialize();
242
+ }
243
+ if (this.onMounted) {
244
+ await this.onMounted();
245
+ }
246
+ }
247
+ /**
248
+ * Web Component lifecycle: called when disconnected from DOM
249
+ */
250
+ disconnectedCallback() {
251
+ // Cleanup is handled by stackViewHidden
252
+ }
253
+ /**
254
+ * Check if the view is initialized
255
+ */
256
+ get isInitialized() {
257
+ return this._isInitialized;
258
+ }
259
+ }
260
+ export function Register(target) {
261
+ target.register();
262
+ return target;
263
+ }
264
+ //# sourceMappingURL=AppView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AppView.js","sourceRoot":"","sources":["../../src/app/AppView.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ import type { RouteParams } from '../navigation/types';
2
+ /**
3
+ * AppView lifecycle hooks
4
+ */
5
+ export interface AppViewLifecycle {
6
+ /**
7
+ * Called before the view is shown
8
+ * Use this to initialize data, fetch from APIs, etc.
9
+ */
10
+ onBeforeMount?(): void | Promise<void>;
11
+ /**
12
+ * Called after the view is mounted to the DOM
13
+ * Use this to set up event listeners, third-party libraries, etc.
14
+ */
15
+ onMounted?(): void | Promise<void>;
16
+ /**
17
+ * Called before the view is unmounted
18
+ * Use this to clean up resources, save state, etc.
19
+ */
20
+ onBeforeUnmount?(): void | Promise<void>;
21
+ /**
22
+ * Called after the view is unmounted from the DOM
23
+ * Final cleanup
24
+ */
25
+ onUnmounted?(): void | Promise<void>;
26
+ /**
27
+ * Called when the state is updated
28
+ * Use this to react to state changes
29
+ */
30
+ onStateChanged?(key: string, value: any): void;
31
+ /**
32
+ * Called when route parameters change while staying on the same view
33
+ * Use this to reload data based on new parameters (e.g., /user/1 -> /user/2)
34
+ * @param newParams - The new route parameters
35
+ * @param oldParams - The previous route parameters
36
+ */
37
+ onParamsChanged?(newParams: RouteParams, oldParams: RouteParams): void | Promise<void>;
38
+ }
39
+ /**
40
+ * AppView configuration options
41
+ */
42
+ export interface AppViewOptions {
43
+ /**
44
+ * Custom transition class for Template.Ts animations
45
+ * Default: 'transition-fade'
46
+ */
47
+ transitionClass?: string;
48
+ /**
49
+ * Whether to automatically update the view on state changes
50
+ * Default: true
51
+ */
52
+ autoUpdate?: boolean;
53
+ /**
54
+ * Whether to use Shadow DOM for the template
55
+ * When true, template is rendered in shadow root (enables <slot> to work properly)
56
+ * Default: false
57
+ */
58
+ useShadowDOM?: boolean;
59
+ }
60
+ /**
61
+ * AppView state object - can be any shape defined by the developer
62
+ */
63
+ export type AppViewState = Record<string, any>;
64
+ /**
65
+ * Template function that returns HTML string
66
+ */
67
+ export type TemplateFunction<T extends AppViewState = AppViewState> = (this: T) => string;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/app/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Main entry point for the application
3
+ * Export all public APIs
4
+ */
5
+ export { App } from './app/App';
6
+ export { AppView, Register } from './app/AppView';
7
+ export { Router } from './navigation/router';
8
+ export { Route } from './navigation/route';
9
+ export type { AppViewLifecycle, AppViewOptions, AppViewState, TemplateFunction } from './app/types';
10
+ export type { RouteParams, RouteGuard, RouteOptions, RouteHandler, RouteDefinition, NavigationEventDetail } from './navigation/types';
11
+ export { NavigationEvents } from './navigation/types';
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Main entry point for the application
3
+ * Export all public APIs
4
+ */
5
+ export { App } from './app/App';
6
+ export { AppView, Register } from './app/AppView';
7
+ export { Router } from './navigation/router';
8
+ export { Route } from './navigation/route';
9
+ export { NavigationEvents } from './navigation/types';
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAmB3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,34 @@
1
+ import type { RouteParams, RouteOptions } from './types';
2
+ /**
3
+ * Represents a route with path pattern matching and parameter extraction
4
+ */
5
+ export declare class Route {
6
+ readonly path: string;
7
+ readonly pattern: RegExp;
8
+ readonly paramNames: string[];
9
+ readonly options: RouteOptions;
10
+ constructor(path: string, options?: RouteOptions);
11
+ /**
12
+ * Convert a path pattern like '/user/:id' to a RegExp
13
+ * Extracts parameter names like 'id'
14
+ */
15
+ private pathToRegExp;
16
+ /**
17
+ * Check if the given path matches this route
18
+ * @param path - The path to test
19
+ * @returns The extracted parameters if match, null otherwise
20
+ */
21
+ match(path: string): RouteParams | null;
22
+ /**
23
+ * Check if navigation is allowed via the route guard
24
+ * @param params - Route parameters
25
+ * @returns true if allowed, false if denied, or redirect path
26
+ */
27
+ canEnter(params: RouteParams): Promise<boolean | string>;
28
+ /**
29
+ * Generate a path from this route pattern with given parameters
30
+ * @param params - Parameters to inject into the path
31
+ * @returns The generated path
32
+ */
33
+ generate(params?: RouteParams): string;
34
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Represents a route with path pattern matching and parameter extraction
3
+ */
4
+ export class Route {
5
+ constructor(path, options = {}) {
6
+ this.path = path;
7
+ this.options = options;
8
+ this.paramNames = [];
9
+ // Convert path pattern to RegExp and extract parameter names
10
+ this.pattern = this.pathToRegExp(path);
11
+ }
12
+ /**
13
+ * Convert a path pattern like '/user/:id' to a RegExp
14
+ * Extracts parameter names like 'id'
15
+ */
16
+ pathToRegExp(path) {
17
+ // Escape special characters except for :param patterns
18
+ const escapedPath = path.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
19
+ // Replace :param with capture groups and extract param names
20
+ const pattern = escapedPath.replace(/:([^/]+)/g, (match, paramName) => {
21
+ this.paramNames.push(paramName);
22
+ return '([^/]+)';
23
+ });
24
+ // Match exact path or with trailing slash
25
+ return new RegExp(`^${pattern}/?$`);
26
+ }
27
+ /**
28
+ * Check if the given path matches this route
29
+ * @param path - The path to test
30
+ * @returns The extracted parameters if match, null otherwise
31
+ */
32
+ match(path) {
33
+ const match = this.pattern.exec(path);
34
+ if (!match) {
35
+ return null;
36
+ }
37
+ // Extract parameters from capture groups
38
+ const params = {};
39
+ this.paramNames.forEach((name, index) => {
40
+ params[name] = decodeURIComponent(match[index + 1]);
41
+ });
42
+ return params;
43
+ }
44
+ /**
45
+ * Check if navigation is allowed via the route guard
46
+ * @param params - Route parameters
47
+ * @returns true if allowed, false if denied, or redirect path
48
+ */
49
+ async canEnter(params) {
50
+ if (!this.options.canEnter) {
51
+ return true;
52
+ }
53
+ return await this.options.canEnter(params);
54
+ }
55
+ /**
56
+ * Generate a path from this route pattern with given parameters
57
+ * @param params - Parameters to inject into the path
58
+ * @returns The generated path
59
+ */
60
+ generate(params = {}) {
61
+ let path = this.path;
62
+ for (const [key, value] of Object.entries(params)) {
63
+ path = path.replace(`:${key}`, encodeURIComponent(value));
64
+ }
65
+ return path;
66
+ }
67
+ }
68
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.js","sourceRoot":"","sources":["../../src/navigation/route.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,OAAO,KAAK;IAMd,YAAY,IAAY,EAAE,UAAwB,EAAE;QAChD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;QAErB,6DAA6D;QAC7D,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAC3C,CAAC;IAED;;;OAGG;IACK,YAAY,CAAC,IAAY;QAC7B,uDAAuD;QACvD,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC;QAE/D,6DAA6D;QAC7D,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE;YAClE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAChC,OAAO,SAAS,CAAC;QACrB,CAAC,CAAC,CAAC;QAEH,0CAA0C;QAC1C,OAAO,IAAI,MAAM,CAAC,IAAI,OAAO,KAAK,CAAC,CAAC;IACxC,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,IAAY;QACd,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEtC,IAAI,CAAC,KAAK,EAAE,CAAC;YACT,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,yCAAyC;QACzC,MAAM,MAAM,GAAgB,EAAE,CAAC;QAC/B,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YACpC,MAAM,CAAC,IAAI,CAAC,GAAG,kBAAkB,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,OAAO,MAAM,CAAC;IAClB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,QAAQ,CAAC,MAAmB;QAC9B,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,OAAO,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC/C,CAAC;IAED;;;;OAIG;IACH,QAAQ,CAAC,SAAsB,EAAE;QAC7B,IAAI,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QAErB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAChD,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,EAAE,EAAE,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC;QAC9D,CAAC;QAED,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ"}
@@ -0,0 +1,92 @@
1
+ import type { RouteParams, RouteOptions, RouteHandler } from './types';
2
+ /**
3
+ * Router class for managing navigation and route mapping
4
+ * Emits events for Application.ts to handle view rendering via StackView
5
+ */
6
+ export declare class Router extends EventTarget {
7
+ private routes;
8
+ private notFoundHandler?;
9
+ private isInitialized;
10
+ private basePath;
11
+ private _currentPath;
12
+ private _currentParams;
13
+ private _currentMeta?;
14
+ constructor();
15
+ /**
16
+ * Map a route path to a handler (view identifier)
17
+ * @param path - The route path (e.g., '/', '/user/:id')
18
+ * @param handler - The view identifier/handler
19
+ * @param options - Optional route configuration
20
+ * @returns The router instance for chaining
21
+ */
22
+ map(path: string, handler: RouteHandler, options?: RouteOptions): this;
23
+ /**
24
+ * Set the 404 not found handler
25
+ * @param handler - The view identifier to use for 404
26
+ * @returns The router instance for chaining
27
+ */
28
+ notFound(handler: RouteHandler): this;
29
+ /**
30
+ * Set the base path for all routes
31
+ * @param base - Base path (e.g., '/basic', '/app')
32
+ * @returns The router instance for chaining
33
+ */
34
+ setBasePath(base: string): this;
35
+ /**
36
+ * Get the current base path
37
+ */
38
+ getBasePath(): string;
39
+ /**
40
+ * Initialize the router and handle the current path
41
+ */
42
+ start(): void;
43
+ /**
44
+ * Navigate to a specific path
45
+ * @param path - The path to navigate to (relative to basePath)
46
+ * @param replaceState - If true, replaces current history entry instead of pushing
47
+ */
48
+ navigate(path: string, replaceState?: boolean): Promise<void>;
49
+ /**
50
+ * Get the current path (route pattern)
51
+ */
52
+ get currentPath(): string;
53
+ /**
54
+ * Get the current route parameters
55
+ */
56
+ get currentParams(): RouteParams;
57
+ /**
58
+ * Get the current route metadata
59
+ */
60
+ get currentMeta(): Record<string, any> | undefined;
61
+ /**
62
+ * Find a route that matches the given path
63
+ */
64
+ private findRoute;
65
+ /**
66
+ * Handle route change (from popstate or initial load)
67
+ */
68
+ private handleRouteChange;
69
+ /**
70
+ * Strip base path from a full path
71
+ */
72
+ private stripBasePath;
73
+ /**
74
+ * Add base path to a route path
75
+ */
76
+ private addBasePath;
77
+ /**
78
+ * Emit navigation event
79
+ */
80
+ private emitNavigation;
81
+ /**
82
+ * Emit not found event
83
+ */
84
+ private emitNotFound;
85
+ /**
86
+ * Generate a URL for a route with parameters
87
+ * @param path - The route path pattern
88
+ * @param params - Parameters to inject
89
+ * @returns The generated URL (with basePath) or null if route not found
90
+ */
91
+ generateUrl(path: string, params?: RouteParams): string | null;
92
+ }