@inglorious/web 2.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.
- package/LICENSE +9 -0
- package/README.md +383 -0
- package/package.json +54 -0
- package/src/form.js +371 -0
- package/src/index.js +10 -0
- package/src/list.js +105 -0
- package/src/mount.js +49 -0
- package/src/router.js +268 -0
- package/types/form.d.ts +377 -0
- package/types/index.d.ts +2 -0
- package/types/mount.d.ts +24 -0
- package/types/router.d.ts +111 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright © 2025 Inglorious Coderz Srl.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
# @inglorious/web
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@inglorious/web)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
A lightweight web framework that combines the entity-based state management of **@inglorious/store** with the performance and simplicity of **lit-html**.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **Seamless Integration**: A simple `mount` function to connect your store, templates, and the DOM.
|
|
13
|
+
- **Efficient Rendering**: Leverages the performance of `lit-html` for DOM updates.
|
|
14
|
+
- **Entity-Based Rendering**: Includes a powerful `api.render(id)` helper to render individual entities based on their type.
|
|
15
|
+
- **Convenient Re-exports**: Provides direct access to `lit-html`'s `html`, `svg`, and core directives.
|
|
16
|
+
- **Client-Side Router**: A simple, entity-based router for single-page applications.
|
|
17
|
+
- **Fully Typed**: Strong TypeScript support for a great developer experience.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @inglorious/web
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### 1. Define Your Store and Entity Renders
|
|
32
|
+
|
|
33
|
+
First, set up your store with entity types. For each type you want to render, add a render method that returns a `lit-html` template.
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
// store.js
|
|
37
|
+
import { createStore, html } from "@inglorious/web"
|
|
38
|
+
|
|
39
|
+
const types = {
|
|
40
|
+
counter: {
|
|
41
|
+
increment(entity, id) {
|
|
42
|
+
if (entity.id !== id) return
|
|
43
|
+
entity.value++
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// Define how a 'counter' entity should be rendered
|
|
47
|
+
render(entity, api) {
|
|
48
|
+
return html`
|
|
49
|
+
<div>
|
|
50
|
+
<span>Count: ${entity.value}</span>
|
|
51
|
+
<button @click=${() => api.notify("increment", entity.id)}>+1</button>
|
|
52
|
+
</div>
|
|
53
|
+
`
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const entities = {
|
|
59
|
+
counter1: { type: "counter", value: 0 },
|
|
60
|
+
counter2: { type: "counter", value: 10 },
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const store = createStore({ types, entities })
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 2. Create Your Root Template and Mount
|
|
67
|
+
|
|
68
|
+
Write a root rendering function that uses the provided api to compose the UI, then use `mount` to attach it to the DOM.
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
// main.js
|
|
72
|
+
import { mount, html } from "@inglorious/web"
|
|
73
|
+
import { store } from "./store.js"
|
|
74
|
+
|
|
75
|
+
// This function receives the API and returns a lit-html template
|
|
76
|
+
const renderApp = (api) => {
|
|
77
|
+
const entities = Object.values(api.getEntities())
|
|
78
|
+
|
|
79
|
+
return html`
|
|
80
|
+
<h1>Counters</h1>
|
|
81
|
+
${entities.map((entity) => api.render(entity.id))}
|
|
82
|
+
`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Mount the app to the DOM
|
|
86
|
+
mount(store, renderApp, document.getElementById("root"))
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The `mount` function subscribes to the store and automatically re-renders your template whenever the state changes.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Client-Side Router
|
|
94
|
+
|
|
95
|
+
`@inglorious/web` includes a lightweight, entity-based client-side router. It integrates directly into your `@inglorious/store` state, allowing your components to reactively update based on the current URL.
|
|
96
|
+
|
|
97
|
+
### 1. Setup the Router
|
|
98
|
+
|
|
99
|
+
To enable the router, add it to your store's types and create a `router` entity. The entity's `routes` property maps URL patterns to the entity types that represent your pages.
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
// store.js
|
|
103
|
+
import { createStore, html, router } from "@inglorious/web"
|
|
104
|
+
|
|
105
|
+
const types = {
|
|
106
|
+
// 1. Add the router type to your store's types
|
|
107
|
+
router,
|
|
108
|
+
|
|
109
|
+
// 2. Define types for your pages
|
|
110
|
+
homePage: {
|
|
111
|
+
render: () => html`<h1>Welcome Home!</h1>`,
|
|
112
|
+
},
|
|
113
|
+
userPage: {
|
|
114
|
+
render: (entity, api) => {
|
|
115
|
+
// Access route params from the router entity
|
|
116
|
+
const { params } = api.getEntity("router")
|
|
117
|
+
return html`<h1>User ${params.id} - ${entity.username}</h1>`
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
notFoundPage: {
|
|
121
|
+
render: () => html`<h1>404 - Page Not Found</h1>`,
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const entities = {
|
|
126
|
+
// 3. Create the router entity
|
|
127
|
+
router: {
|
|
128
|
+
type: "router",
|
|
129
|
+
routes: {
|
|
130
|
+
"/": "homePage",
|
|
131
|
+
"/users/:id": "userPage",
|
|
132
|
+
"*": "notFoundPage", // Fallback for unmatched routes
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
userPage: {
|
|
136
|
+
type: "userPage",
|
|
137
|
+
username: "Alice",
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export const store = createStore({ types, entities })
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 2. Render the Current Route
|
|
145
|
+
|
|
146
|
+
In your root template, read the `route` property from the router entity and use `api.render()` to display the correct page.
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
// main.js
|
|
150
|
+
import { mount, html } from "@inglorious/web"
|
|
151
|
+
import { store } from "./store.js"
|
|
152
|
+
|
|
153
|
+
const renderApp = (api) => {
|
|
154
|
+
const { route } = api.getEntity("router") // e.g., "homePage" or "userPage"
|
|
155
|
+
|
|
156
|
+
return html`
|
|
157
|
+
<nav><a href="/">Home</a> | <a href="/users/123">User 123</a></nav>
|
|
158
|
+
<main>${route ? api.render(route) : ""}</main>
|
|
159
|
+
`
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
mount(store, renderApp, document.getElementById("root"))
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The router automatically intercepts clicks on local `<a>` tags and handles browser back/forward events, keeping your UI in sync with the URL.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Forms
|
|
170
|
+
|
|
171
|
+
`@inglorious/web` includes a small but powerful `form` type for managing form state inside your entity store. It offers:
|
|
172
|
+
|
|
173
|
+
- Declarative form state held on an entity (`initialValues`, `values`, `errors`, `touched`)
|
|
174
|
+
- Convenient helpers for reading field value/error/touched state (`getFieldValue`, `getFieldError`, `isFieldTouched`)
|
|
175
|
+
- Built-in handlers for field changes, blurs, array fields, sync/async validation and submission
|
|
176
|
+
|
|
177
|
+
### Add the `form` type
|
|
178
|
+
|
|
179
|
+
Include `form` in your `types` and create an entity for the form (use any id you like — `form` is used below for clarity):
|
|
180
|
+
|
|
181
|
+
```javascript
|
|
182
|
+
import { createStore, form } from "@inglorious/web"
|
|
183
|
+
|
|
184
|
+
const types = { form }
|
|
185
|
+
|
|
186
|
+
const entities = {
|
|
187
|
+
form: {
|
|
188
|
+
type: "form",
|
|
189
|
+
initialValues: {
|
|
190
|
+
name: "",
|
|
191
|
+
email: "",
|
|
192
|
+
addresses: [],
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const store = createStore({ types, entities })
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### How it works (events & helpers)
|
|
201
|
+
|
|
202
|
+
The `form` type listens for a simple set of events (target the specific entity id with `#<id>:<event>`):
|
|
203
|
+
|
|
204
|
+
- `#<id>:fieldChange` — payload { path, value, validate? } — set a field value and optionally run a single-field validator
|
|
205
|
+
- `#<id>:fieldBlur` — payload { path, validate? } — mark field touched and optionally validate on blur
|
|
206
|
+
- `#<id>:fieldArrayAppend|fieldArrayRemove|fieldArrayInsert|fieldArrayMove` — manipulate array fields
|
|
207
|
+
- `#<id>:reset` — reset the form to `initialValues`
|
|
208
|
+
- `#<id>:validate` — synchronous whole-form validation; payload { validate }
|
|
209
|
+
- `#<id>:validateAsync` — async whole-form validation; payload { validate }
|
|
210
|
+
- `#<id>:submit` — typically handled by your `form` type's `submit` method (implement custom behavior there)
|
|
211
|
+
|
|
212
|
+
Helpers available from the package let you read state from templates and field helper components:
|
|
213
|
+
|
|
214
|
+
- `getFieldValue(formEntity, path)` — read a nested field value
|
|
215
|
+
- `getFieldError(formEntity, path)` — read a nested field's error message
|
|
216
|
+
- `isFieldTouched(formEntity, path)` — check if a field has been touched
|
|
217
|
+
|
|
218
|
+
Form state includes helpful flags:
|
|
219
|
+
|
|
220
|
+
- `isPristine` — whether the form has changed from initial values
|
|
221
|
+
- `isValid` — whether the current form has no validation errors
|
|
222
|
+
- `isValidating` — whether async validation is in progress
|
|
223
|
+
- `isSubmitting` — whether submission is in progress
|
|
224
|
+
- `submitError` — an optional submission-level error message
|
|
225
|
+
|
|
226
|
+
### Simple example (from examples/apps/web-form)
|
|
227
|
+
|
|
228
|
+
Field components typically call `api.notify` and the `form` entity reacts accordingly. Example input field usage:
|
|
229
|
+
|
|
230
|
+
```javascript
|
|
231
|
+
// inside a field component render
|
|
232
|
+
@input=${(e) => api.notify(`#${entity.id}:fieldChange`, { path: 'name', value: e.target.value, validate: validateName })}
|
|
233
|
+
@blur=${() => api.notify(`#${entity.id}:fieldBlur`, { path: 'name', validate: validateName })}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Submissions and whole-form validation can be triggered from a `form` render:
|
|
237
|
+
|
|
238
|
+
```javascript
|
|
239
|
+
<form @submit=${() => { api.notify(`#form:validate`, { validate: validateForm }); api.notify(`#form:submit`) }}>
|
|
240
|
+
<!-- inputs / buttons -->
|
|
241
|
+
</form>
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
For a complete, working demo and helper components look at `examples/apps/web-form` which ships with the repository.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Virtualized lists
|
|
249
|
+
|
|
250
|
+
`@inglorious/web` provides a small virtualized `list` type to efficiently render very long lists by only keeping visible items in the DOM. The `list` type is useful when you need to display large datasets without paying the full cost of mounting every element at once.
|
|
251
|
+
|
|
252
|
+
Key features:
|
|
253
|
+
|
|
254
|
+
- Renders only the visible slice of items and positions them absolutely inside a scrolling container.
|
|
255
|
+
- Automatically measures the first visible item height when not provided.
|
|
256
|
+
- Efficient scroll handling with simple buffer controls to avoid visual gaps.
|
|
257
|
+
|
|
258
|
+
### Typical entity shape
|
|
259
|
+
|
|
260
|
+
When you add the `list` type to your store the entity can include these properties (the type will provide sensible defaults). Only `items` is required — all other properties are optional:
|
|
261
|
+
|
|
262
|
+
- `items` (Array) — the dataset to render.
|
|
263
|
+
- `visibleRange` ({ start, end }) — current visible slice indices.
|
|
264
|
+
- `viewportHeight` (number) — height of the scrolling viewport in pixels.
|
|
265
|
+
- `itemHeight` (number | null) — fixed height for each item (when null, the type will measure the first item and use an estimated height).
|
|
266
|
+
- `estimatedHeight` (number) — fallback height used before measurement.
|
|
267
|
+
- `bufferSize` (number) — extra items to render before/after the visible range to reduce flicker during scrolling.
|
|
268
|
+
|
|
269
|
+
### Events & methods
|
|
270
|
+
|
|
271
|
+
The `list` type listens for the following events on the target entity:
|
|
272
|
+
|
|
273
|
+
- `#<id>:scroll` — payload is the scrolling container; updates `visibleRange` based on scroll position.
|
|
274
|
+
- `#<id>:measureHeight` — payload is the container element; used internally to measure the first item and compute `itemHeight`.
|
|
275
|
+
|
|
276
|
+
It also expects the item type to export `renderItem(item, index, api)` so each visible item can be rendered using the project's entity-based render approach.
|
|
277
|
+
|
|
278
|
+
### Example
|
|
279
|
+
|
|
280
|
+
Minimal example showing how to extend the `list` type to create a domain-specific list (e.g. `productList`) and provide a `renderItem(item, index, api)` helper.
|
|
281
|
+
|
|
282
|
+
```javascript
|
|
283
|
+
import { createStore, html, list } from "@inglorious/web"
|
|
284
|
+
|
|
285
|
+
// Extend the built-in list type to render product items
|
|
286
|
+
const productList = {
|
|
287
|
+
...list,
|
|
288
|
+
|
|
289
|
+
renderItem(item, index) {
|
|
290
|
+
return html`<div class="product">
|
|
291
|
+
${index}: <strong>${item.name}</strong> — ${item.price}
|
|
292
|
+
</div>`
|
|
293
|
+
},
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const types = { list: productList }
|
|
297
|
+
|
|
298
|
+
const entities = {
|
|
299
|
+
products: {
|
|
300
|
+
type: "list",
|
|
301
|
+
items: Array.from({ length: 10000 }, (_, i) => ({
|
|
302
|
+
name: `Product ${i}`,
|
|
303
|
+
price: `$${i}`,
|
|
304
|
+
})),
|
|
305
|
+
viewportHeight: 400,
|
|
306
|
+
estimatedHeight: 40,
|
|
307
|
+
bufferSize: 5,
|
|
308
|
+
},
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const store = createStore({ types, entities })
|
|
312
|
+
|
|
313
|
+
// Render with api.render(entity.id) as usual — the list will call productList.renderItem for each visible item.
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
See `src/list.js` in the package for the implementation details and the `examples/apps/web-list` demo for a complete working example. In the demo the `productList` type extends the `list` type and provides `renderItem(item, index)` to render each visible item — see `examples/apps/web-list/src/product-list/product-list.js`.
|
|
317
|
+
|
|
318
|
+
### 3. Programmatic Navigation
|
|
319
|
+
|
|
320
|
+
To navigate from your JavaScript code, dispatch a `navigate` event.
|
|
321
|
+
|
|
322
|
+
```javascript
|
|
323
|
+
api.notify("navigate", "/users/456")
|
|
324
|
+
|
|
325
|
+
// Or navigate back in history
|
|
326
|
+
api.notify("navigate", -1)
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## API Reference
|
|
330
|
+
|
|
331
|
+
**`mount(store, renderFn, element)`**
|
|
332
|
+
|
|
333
|
+
Connects a store to a `lit-html` template and renders it into a DOM element. It automatically handles re-rendering on state changes.
|
|
334
|
+
|
|
335
|
+
**Parameters:**
|
|
336
|
+
|
|
337
|
+
- `store` (required): An instance of `@inglorious/store`.
|
|
338
|
+
- `renderFn(api)` (required): A function that takes an `api` object and returns a `lit-html` `TemplateResult` or `null`.
|
|
339
|
+
- `element` (required): The `HTMLElement` or `DocumentFragment` to render the template into.
|
|
340
|
+
|
|
341
|
+
**Returns:**
|
|
342
|
+
|
|
343
|
+
- `() => void`: An `unsubscribe` function to stop listening to store updates and clean up.
|
|
344
|
+
|
|
345
|
+
### The `api` Object
|
|
346
|
+
|
|
347
|
+
The `renderFn` receives a powerful `api` object that contains all methods from the store's API (`getEntities`, `getEntity`, `notify`, etc.) plus a special `render(id)` method.
|
|
348
|
+
|
|
349
|
+
This `render(id)` method is the cornerstone of entity-based rendering. It looks up an entity by its `id`, finds its corresponding type definition, and calls the `render(entity, api)` method on that type. This allows you to define rendering logic alongside an entity's other behaviors.
|
|
350
|
+
|
|
351
|
+
### Re-exported `lit-html` Utilities
|
|
352
|
+
|
|
353
|
+
For convenience, `@inglorious/web` re-exports the most common utilities from `@inglorious/store` and `lit-html`, so you only need one import.
|
|
354
|
+
|
|
355
|
+
```javascript
|
|
356
|
+
import {
|
|
357
|
+
createStore,
|
|
358
|
+
creteDevtools,
|
|
359
|
+
createSelector,
|
|
360
|
+
mount,
|
|
361
|
+
html,
|
|
362
|
+
router,
|
|
363
|
+
render,
|
|
364
|
+
svg,
|
|
365
|
+
classMap,
|
|
366
|
+
} from "@inglorious/web"
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## License
|
|
372
|
+
|
|
373
|
+
**MIT License - Free and open source**
|
|
374
|
+
|
|
375
|
+
Created by [Matteo Antony Mistretta](https://github.com/IngloriousCoderz)
|
|
376
|
+
|
|
377
|
+
You're free to use, modify, and distribute this software. See [LICENSE](./LICENSE) for details.
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## Contributing
|
|
382
|
+
|
|
383
|
+
Contributions welcome! Please read our [Contributing Guidelines](../../CONTRIBUTING.md) first.
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@inglorious/web",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "A new web framework that leverages the power of the Inglorious Store combined with the performance and simplicity of lit-html.",
|
|
5
|
+
"author": "IceOnFire <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/IngloriousCoderz/inglorious-engine.git",
|
|
10
|
+
"directory": "packages/web"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/IngloriousCoderz/inglorious-engine/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"lit-html",
|
|
17
|
+
"state",
|
|
18
|
+
"state-management",
|
|
19
|
+
"store",
|
|
20
|
+
"redux",
|
|
21
|
+
"inglorious-store"
|
|
22
|
+
],
|
|
23
|
+
"type": "module",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./types/index.d.ts",
|
|
27
|
+
"import": "./src/index.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"src",
|
|
32
|
+
"types"
|
|
33
|
+
],
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"lit-html": "^3.3.1",
|
|
39
|
+
"@inglorious/store": "7.0.0",
|
|
40
|
+
"@inglorious/utils": "3.7.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"prettier": "^3.6.2",
|
|
44
|
+
"vite": "^7.1.3",
|
|
45
|
+
"@inglorious/eslint-config": "1.0.1"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">= 22"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"format": "prettier --write '**/*.{js,jsx}'",
|
|
52
|
+
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0"
|
|
53
|
+
}
|
|
54
|
+
}
|