@iyulab/router 0.6.2 → 0.7.1

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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 iyulab
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1
+ MIT License
2
+
3
+ Copyright (c) 2025 iyulab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
package/README.md CHANGED
@@ -1,149 +1,272 @@
1
- # @iyulab/router
2
-
3
- A modern, lightweight client-side router for web applications with support for both Lit and React components.
4
-
5
- ## Features
6
-
7
- - 🚀 **Modern URLPattern-based routing** - Uses native URLPattern API for powerful path matching
8
- - 🔧 **Unified Framework Support** - Works with both Lit and React components using render functions
9
- - 📱 **Client-Side Navigation** - History API integration with browser back/forward support
10
- - 🎯 **Nested Routing** - Support for deeply nested route hierarchies with index and path routes
11
- - 📊 **Route Events** - Track navigation progress with route-begin, route-done, and route-error events
12
- - ⚠️ **Enhanced Error Handling** - Built-in ErrorPage component with improved styling
13
-
14
- ## Installation
15
-
16
- ```bash
17
- npm install @iyulab/router
18
- ```
19
-
20
- ## Quick Start
21
-
22
- ### Basic Setup
23
-
24
- ```typescript
25
- import { Router } from '@iyulab/router';
26
- import { html } from 'lit';
27
-
28
- const router = new Router({
29
- basepath: '/',
30
- routes: [
31
- {
32
- index: true,
33
- render: () => html`<home-page></home-page>`
34
- },
35
- {
36
- path: '/user/:id', // URLPattern route
37
- render: (routeInfo) => html`<user-page .userId=${routeInfo.params.id}></user-page>`
38
- }
39
- ],
40
- });
41
- ```
42
-
43
- ### Mixed Framework Support
44
-
45
- ```typescript
46
- import React from 'react';
47
-
48
- const routes = [
49
- // Lit component
50
- {
51
- path: '/lit-page',
52
- render: (routeInfo) => {
53
- return html`<my-lit-component .routeInfo=${routeInfo}></my-lit-component>`
54
- }
55
- },
56
- // React component
57
- {
58
- path: '/react-page',
59
- render: (routeInfo) => {
60
- return ( <MyComponent></MyComponent> )
61
- }
62
- },
63
- // HTML element
64
- {
65
- path: '/element-page',
66
- render: (routeInfo) => {
67
- const element = document.createElement('my-element');
68
- element.data = routeInfo.params;
69
- return element;
70
- }
71
- }
72
- ];
73
- ```
74
-
75
- ### Nested Routes
76
-
77
- ```typescript
78
- import { RouteConfig } from '@iyulab/router';
79
-
80
- const routes: RouteConfig[] = [
81
- {
82
- path: '/dashboard',
83
- render: () => html`<dashboard-layout><u-outlet></u-outlet></dashboard-layout>`,
84
- children: [
85
- {
86
- index: true, // Matches '/dashboard'
87
- render: () => html`<dashboard-home></dashboard-home>`
88
- },
89
- {
90
- path: 'settings', // Matches '/dashboard/settings'
91
- render: () => html`<dashboard-settings></dashboard-settings>`
92
- }
93
- ]
94
- }
95
- ];
96
- ```
97
-
98
- ## Usage Examples
99
-
100
- ### Using with Lit Elements
101
-
102
- ```typescript
103
- import { LitElement, html } from 'lit';
104
- import { customElement } from 'lit/decorators.js';
105
-
106
- import "@iyulab/router";
107
-
108
- @customElement('app-root')
109
- export class AppRoot extends LitElement {
110
- render() {
111
- return html`
112
- <nav>
113
- <u-link href="/">Home</u-link>
114
- <u-link href="/about">About</u-link>
115
- <u-link href="/user/123">User Profile</u-link>
116
- </nav>
117
- <main>
118
- <u-outlet></u-outlet>
119
- </main>
120
- `;
121
- }
122
- }
123
- ```
124
-
125
- ### Using with React Components
126
-
127
- ```tsx
128
- import React from 'react';
129
- import { UOutlet, ULink } from '@iyulab/router/react';
130
-
131
- export function AppRoot() {
132
- return (
133
- <div>
134
- <nav>
135
- <ULink href="/">Home</ULink>
136
- <ULink href="/about">About</ULink>
137
- <ULink href="/user/123">User Profile</ULink>
138
- </nav>
139
- <main>
140
- <UOutlet />
141
- </main>
142
- </div>
143
- );
144
- }
145
- ```
146
-
147
- ## License
148
-
149
- MIT License - see [LICENSE](LICENSE) file for details.
1
+ # @iyulab/router
2
+
3
+ A modern, lightweight client-side router for web applications with support for both Lit and React components.
4
+
5
+ ## Features
6
+
7
+ - 🚀 **Modern URLPattern-based routing** - Uses native URLPattern API for powerful path matching
8
+ - 🔧 **Unified Framework Support** - Works with both Lit and React components using render functions
9
+ - 📱 **Client-Side Navigation** - History API integration with browser back/forward support
10
+ - 🎯 **Nested Routing** - Support for deeply nested route hierarchies with index and path routes
11
+ - 📊 **Route Events** - Track navigation progress with route-begin, route-done, and route-error events
12
+ - ⚠️ **Enhanced Error Handling** - Built-in ErrorPage component with improved styling
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @iyulab/router
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ### Basic Setup
23
+
24
+ ```typescript
25
+ import { Router } from '@iyulab/router';
26
+ import { html } from 'lit';
27
+
28
+ const router = new Router({
29
+ basepath: '/',
30
+ routes: [
31
+ {
32
+ index: true,
33
+ render: () => html`<home-page></home-page>`
34
+ },
35
+ {
36
+ path: '/user/:id', // URLPattern route
37
+ render: (routeInfo) => html`<user-page .userId=${routeInfo.params.id}></user-page>`
38
+ }
39
+ ],
40
+ });
41
+ ```
42
+
43
+ ### Mixed Framework Support
44
+
45
+ ```typescript
46
+ import React from 'react';
47
+
48
+ const routes = [
49
+ // Lit component
50
+ {
51
+ path: '/lit-page',
52
+ render: (routeInfo) => {
53
+ return html`<my-lit-component .routeInfo=${routeInfo}></my-lit-component>`
54
+ }
55
+ },
56
+ // React component
57
+ {
58
+ path: '/react-page',
59
+ render: (routeInfo) => {
60
+ return ( <MyComponent></MyComponent> )
61
+ }
62
+ },
63
+ // HTML element
64
+ {
65
+ path: '/element-page',
66
+ render: (routeInfo) => {
67
+ const element = document.createElement('my-element');
68
+ element.data = routeInfo.params;
69
+ return element;
70
+ }
71
+ }
72
+ ];
73
+ ```
74
+
75
+ ### Nested Routes
76
+
77
+ ```typescript
78
+ import { RouteConfig } from '@iyulab/router';
79
+
80
+ const routes: RouteConfig[] = [
81
+ {
82
+ path: '/dashboard',
83
+ render: () => html`<dashboard-layout><u-outlet></u-outlet></dashboard-layout>`,
84
+ children: [
85
+ {
86
+ index: true, // Matches '/dashboard'
87
+ render: () => html`<dashboard-home></dashboard-home>`
88
+ },
89
+ {
90
+ path: 'settings', // Matches '/dashboard/settings'
91
+ render: () => html`<dashboard-settings></dashboard-settings>`
92
+ }
93
+ ]
94
+ }
95
+ ];
96
+ ```
97
+
98
+ ## Usage Examples
99
+
100
+ ### Using with Lit Elements
101
+
102
+ ```typescript
103
+ import { LitElement, html } from 'lit';
104
+ import { customElement } from 'lit/decorators.js';
105
+
106
+ import "@iyulab/router";
107
+
108
+ @customElement('app-root')
109
+ export class AppRoot extends LitElement {
110
+ render() {
111
+ return html`
112
+ <nav>
113
+ <u-link href="/">Home</u-link>
114
+ <u-link href="/about">About</u-link>
115
+ <u-link href="/user/123">User Profile</u-link>
116
+ </nav>
117
+ <main>
118
+ <u-outlet></u-outlet>
119
+ </main>
120
+ `;
121
+ }
122
+ }
123
+ ```
124
+
125
+ ### Using with React Components
126
+
127
+ ```tsx
128
+ import React from 'react';
129
+ import { UOutlet, ULink } from '@iyulab/router/react';
130
+
131
+ export function AppRoot() {
132
+ return (
133
+ <div>
134
+ <nav>
135
+ <ULink href="/">Home</ULink>
136
+ <ULink href="/about">About</ULink>
137
+ <ULink href="/user/123">User Profile</ULink>
138
+ </nav>
139
+ <main>
140
+ <UOutlet />
141
+ </main>
142
+ </div>
143
+ );
144
+ }
145
+ ```
146
+
147
+ ## Error Handling
148
+
149
+ The router provides comprehensive error handling through `FallbackRouteContext`. When a routing error occurs, the fallback render function receives a context with full error information:
150
+
151
+ ```typescript
152
+ const router = new Router({
153
+ root: document.body,
154
+ basepath: '/',
155
+ routes: [...],
156
+ fallback: {
157
+ title: 'Error',
158
+ render: (ctx) => {
159
+ // ctx.error contains RouteError with code, message, and original error
160
+ const { code, message, original } = ctx.error;
161
+
162
+ if (code === 'NOT_FOUND') {
163
+ return html`<not-found-page .path=${ctx.pathname}></not-found-page>`;
164
+ }
165
+ if (code === 'CONTENT_LOAD_ERROR') {
166
+ return html`<error-page .message=${message}></error-page>`;
167
+ }
168
+ return html`<error-page .error=${ctx.error}></error-page>`;
169
+ }
170
+ }
171
+ });
172
+ ```
173
+
174
+ Error types:
175
+ - `NotFoundError` — No matching route found (code: `NOT_FOUND`)
176
+ - `ContentLoadError` — Route render function threw an error (code: `CONTENT_LOAD_ERROR`)
177
+ - `ContentRenderError` — Outlet rendering failed (code: `CONTENT_RENDER_ERROR`)
178
+
179
+ ## Route Metadata
180
+
181
+ Routes can carry arbitrary metadata via the `meta` field. When a route matches, metadata from the entire matched route chain is merged (parent → child order, child overrides parent):
182
+
183
+ ```typescript
184
+ const router = new Router({
185
+ root: document.body,
186
+ basepath: '/',
187
+ routes: [
188
+ {
189
+ path: '/admin',
190
+ meta: { requiresAuth: true, layout: 'admin' },
191
+ render: (ctx) => {
192
+ // ctx.meta === { requiresAuth: true, layout: 'admin' }
193
+ return html`<admin-layout><u-outlet></u-outlet></admin-layout>`;
194
+ },
195
+ children: [
196
+ {
197
+ path: 'settings',
198
+ meta: { requiresAuth: true, role: 'superadmin' },
199
+ render: (ctx) => {
200
+ // ctx.meta === { requiresAuth: true, layout: 'admin', role: 'superadmin' }
201
+ return html`<admin-settings></admin-settings>`;
202
+ }
203
+ }
204
+ ]
205
+ }
206
+ ]
207
+ });
208
+ ```
209
+
210
+ Use cases: authentication guards, SEO tags, analytics tracking, layout selection, and more.
211
+
212
+ ## Route Events
213
+
214
+ The router dispatches events on the `window` object during navigation:
215
+
216
+ | Event | Type | Description |
217
+ |-------|------|-------------|
218
+ | `route-begin` | `RouteBeginEvent` | Fired when navigation starts |
219
+ | `route-progress` | `RouteProgressEvent` | Fired during async loading (0–100) |
220
+ | `route-done` | `RouteDoneEvent` | Fired when navigation completes successfully |
221
+ | `route-error` | `RouteErrorEvent` | Fired when a routing error occurs |
222
+
223
+ ```typescript
224
+ // Track navigation progress
225
+ window.addEventListener('route-progress', (e: RouteProgressEvent) => {
226
+ progressBar.value = e.progress;
227
+ });
228
+
229
+ // Log navigation events
230
+ window.addEventListener('route-begin', (e: RouteBeginEvent) => {
231
+ console.log('Navigating to:', e.context.pathname);
232
+ });
233
+
234
+ window.addEventListener('route-done', (e: RouteDoneEvent) => {
235
+ analytics.trackPageView(e.context.pathname);
236
+ });
237
+
238
+ window.addEventListener('route-error', (e: RouteErrorEvent) => {
239
+ errorTracker.report(e.error);
240
+ });
241
+ ```
242
+
243
+ ## URL Parameters
244
+
245
+ The router supports URLPattern-based parameter matching:
246
+
247
+ ```typescript
248
+ const routes: RouteConfig[] = [
249
+ // Required parameter
250
+ { path: '/user/:id', render: (ctx) => html`<user-page .id=${ctx.params.id}></user-page>` },
251
+
252
+ // Optional parameter
253
+ { path: '/posts/:category?', render: (ctx) => {
254
+ const category = ctx.params.category || 'all';
255
+ return html`<posts-page .category=${category}></posts-page>`;
256
+ }},
257
+
258
+ // Wildcard (catch-all)
259
+ { path: '/docs/:path*', render: (ctx) => html`<docs-page .path=${ctx.params.path}></docs-page>` },
260
+
261
+ // Multiple parameters
262
+ { path: '/org/:orgId/repo/:repoId', render: (ctx) => {
263
+ return html`<repo-page .orgId=${ctx.params.orgId} .repoId=${ctx.params.repoId}></repo-page>`;
264
+ }}
265
+ ];
266
+ ```
267
+
268
+ When URL parameters change (e.g., navigating from `/user/1` to `/user/2`), leaf routes (without children) automatically re-render since `force` defaults to `true`. For parent routes with children, set `force: true` explicitly if re-rendering is needed on parameter changes.
269
+
270
+ ## License
271
+
272
+ MIT License - see [LICENSE](LICENSE) file for details.
package/dist/index.d.ts CHANGED
@@ -60,6 +60,15 @@ declare interface BaseRouteConfig {
60
60
  * @default false
61
61
  */
62
62
  ignoreCase?: boolean;
63
+ /**
64
+ * 라우트에 연결할 메타데이터
65
+ * - 인증, SEO, 분석 등의 용도로 사용할 수 있습니다.
66
+ * @example
67
+ * ```typescript
68
+ * { path: '/admin', meta: { requiresAuth: true, role: 'admin' } }
69
+ * ```
70
+ */
71
+ meta?: Record<string, unknown>;
63
72
  }
64
73
 
65
74
  /**
@@ -237,6 +246,11 @@ export declare interface RouteContext {
237
246
  * @param value 진행 상태 값 (0~100)
238
247
  */
239
248
  progress: (value: number) => void;
249
+ /**
250
+ * 매칭된 라우트 체인의 병합된 메타데이터
251
+ * - 부모 라우트에서 자식 라우트 순서로 병합됩니다.
252
+ */
253
+ meta: Record<string, unknown>;
240
254
  }
241
255
 
242
256
  /**
@@ -437,11 +451,11 @@ export declare class UOutlet extends HTMLElement {
437
451
 
438
452
  export { }
439
453
 
440
- declare global {
441
- interface WindowEventMap {
442
- 'route-begin': RouteBeginEvent;
443
- 'route-progress': RouteProgressEvent;
444
- 'route-done': RouteDoneEvent;
445
- 'route-error': RouteErrorEvent;
446
- }
454
+ declare global {
455
+ interface WindowEventMap {
456
+ 'route-begin': RouteBeginEvent;
457
+ 'route-progress': RouteProgressEvent;
458
+ 'route-done': RouteDoneEvent;
459
+ 'route-error': RouteErrorEvent;
460
+ }
447
461
  }
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { a as absolutePath, i as isExternalUrl, p as parseUrl } from "./share-6Zs4TunA.js";
2
- import { b, U } from "./share-6Zs4TunA.js";
1
+ import { a as absolutePath, i as isExternalUrl, p as parseUrl } from "./share-B5lysqp2.js";
2
+ import { b, U } from "./share-B5lysqp2.js";
3
3
  import { css, LitElement, html } from "lit";
4
4
  import { property, customElement } from "lit/decorators.js";
5
5
  class RouteError extends Error {
@@ -190,6 +190,7 @@ function getRandomID() {
190
190
  }
191
191
  function findOutlet(element) {
192
192
  if (!element) return void 0;
193
+ if (element.tagName === "U-OUTLET") return element;
193
194
  let outlet = void 0;
194
195
  if (element.shadowRoot) {
195
196
  outlet = element.shadowRoot.querySelector("u-outlet");
@@ -222,7 +223,9 @@ async function waitOutlet(element, timeout = 1e4) {
222
223
  if (outlet) return outlet;
223
224
  await new Promise((r) => setTimeout(r, 50));
224
225
  }
225
- throw new Error("Timed out waiting for u-outlet.");
226
+ throw new Error(
227
+ `Timed out waiting for <u-outlet> inside <${element.tagName.toLowerCase()}>. Ensure that the router root element contains a <u-outlet> child.`
228
+ );
226
229
  }
227
230
  function findAnchorFrom(event) {
228
231
  const targets = event.composedPath() || [];
@@ -370,6 +373,11 @@ class Router {
370
373
  if (lastRoute && lastRoute.path instanceof URLPattern) {
371
374
  context.params = lastRoute.path.exec({ pathname: context.pathname })?.pathname.groups || {};
372
375
  }
376
+ const mergedMeta = {};
377
+ for (const route of routes) {
378
+ if (route.meta) Object.assign(mergedMeta, route.meta);
379
+ }
380
+ context.meta = mergedMeta;
373
381
  this._context = context;
374
382
  outlet = findOutletOrThrow(this._rootElement);
375
383
  let title = void 0;
package/dist/react.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
  import { createComponent } from "@lit/react";
3
- import { b as ULink$1, U as UOutlet$1 } from "./share-6Zs4TunA.js";
3
+ import { b as ULink$1, U as UOutlet$1 } from "./share-B5lysqp2.js";
4
4
  const ULink = createComponent({
5
5
  react: React,
6
6
  tagName: "u-link",
@@ -78,7 +78,8 @@ function parseUrl(url, basepath) {
78
78
  hash: urlObj.hash,
79
79
  params: {},
80
80
  progress: () => {
81
- }
81
+ },
82
+ meta: {}
82
83
  };
83
84
  }
84
85
  function absolutePath(...paths) {
package/package.json CHANGED
@@ -1,56 +1,56 @@
1
- {
2
- "name": "@iyulab/router",
3
- "version": "0.6.2",
4
- "description": "A modern client-side router for web applications with support for Lit and React components",
5
- "keywords": [
6
- "lit",
7
- "react",
8
- "router",
9
- "routing",
10
- "spa",
11
- "navigation",
12
- "client-side"
13
- ],
14
- "license": "MIT",
15
- "author": "iyulab",
16
- "repository": {
17
- "type": "git",
18
- "url": "https://github.com/iyulab/node-router.git"
19
- },
20
- "files": [
21
- "dist",
22
- "package.json",
23
- "README.md",
24
- "LICENSE"
25
- ],
26
- "type": "module",
27
- "types": "dist/index.d.ts",
28
- "exports": {
29
- ".": {
30
- "types": "./dist/index.d.ts",
31
- "import": "./dist/index.js"
32
- },
33
- "./react": {
34
- "types": "./dist/react.d.ts",
35
- "import": "./dist/react.js"
36
- }
37
- },
38
- "scripts": {
39
- "test": "vite",
40
- "build": "vite build"
41
- },
42
- "dependencies": {
43
- "@lit/react": "^1.0.8",
44
- "lit": "^3.3.2",
45
- "react": "^19.2.3",
46
- "react-dom": "^19.2.3"
47
- },
48
- "devDependencies": {
49
- "@types/node": "^25.0.9",
50
- "@types/react": "^19.2.9",
51
- "@types/react-dom": "^19.2.3",
52
- "typescript": "^5.9.3",
53
- "vite": "^7.3.1",
54
- "vite-plugin-dts": "^4.5.4"
55
- }
56
- }
1
+ {
2
+ "name": "@iyulab/router",
3
+ "version": "0.7.1",
4
+ "description": "A modern client-side router for web applications with support for Lit and React components",
5
+ "keywords": [
6
+ "lit",
7
+ "react",
8
+ "router",
9
+ "routing",
10
+ "spa",
11
+ "navigation",
12
+ "client-side"
13
+ ],
14
+ "license": "MIT",
15
+ "author": "iyulab",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/iyulab/node-router.git"
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "package.json",
23
+ "README.md",
24
+ "LICENSE"
25
+ ],
26
+ "type": "module",
27
+ "types": "dist/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./src/index.d.ts",
31
+ "import": "./src/index.js"
32
+ },
33
+ "./react": {
34
+ "types": "./dist/react.d.ts",
35
+ "import": "./dist/react.js"
36
+ }
37
+ },
38
+ "scripts": {
39
+ "test": "vite",
40
+ "build": "vite build"
41
+ },
42
+ "dependencies": {
43
+ "@lit/react": "^1.0.8",
44
+ "lit": "^3.3.2",
45
+ "react": "^19.2.3",
46
+ "react-dom": "^19.2.3"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^25.0.9",
50
+ "@types/react": "^19.2.9",
51
+ "@types/react-dom": "^19.2.3",
52
+ "typescript": "^5.9.3",
53
+ "vite": "^7.3.1",
54
+ "vite-plugin-dts": "^4.5.4"
55
+ }
56
+ }