@inglorious/web 2.0.1 → 2.1.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/README.md +71 -11
- package/package.json +4 -2
- package/src/index.js +3 -1
- package/src/list.js +6 -2
- package/src/table/base.css +34 -0
- package/src/table/filters/date.js +42 -0
- package/src/table/filters/number.js +22 -0
- package/src/table/filters/range.js +42 -0
- package/src/table/filters/select.js +34 -0
- package/src/table/filters/text.js +22 -0
- package/src/table/filters.js +24 -0
- package/src/table/logic.js +401 -0
- package/src/table/rendering.js +239 -0
- package/src/table/theme.css +83 -0
- package/src/table.js +4 -369
package/README.md
CHANGED
|
@@ -164,6 +164,77 @@ mount(store, renderApp, document.getElementById("root"))
|
|
|
164
164
|
|
|
165
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
166
|
|
|
167
|
+
### 3. Programmatic Navigation
|
|
168
|
+
|
|
169
|
+
To navigate from your JavaScript code, dispatch a `navigate` event.
|
|
170
|
+
|
|
171
|
+
```javascript
|
|
172
|
+
api.notify("navigate", "/users/456")
|
|
173
|
+
|
|
174
|
+
// Or navigate back in history
|
|
175
|
+
api.notify("navigate", -1)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Table
|
|
181
|
+
|
|
182
|
+
`@inglorious/web` includes a `table` type for displaying data in a tabular format. It's designed to be flexible and customizable.
|
|
183
|
+
|
|
184
|
+
### 1. Add the `table` type
|
|
185
|
+
|
|
186
|
+
To use it, import the `table` type and its CSS, then create an entity for your table. You must define the `data` to be displayed and can optionally provide `columns` definitions.
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
// In your entity definition file
|
|
190
|
+
import { table } from "@inglorious/web"
|
|
191
|
+
|
|
192
|
+
// Import base styles and a theme. You can create your own theme.
|
|
193
|
+
import "@inglorious/web/table/base.css"
|
|
194
|
+
import "@inglorious/web/table/theme.css"
|
|
195
|
+
|
|
196
|
+
export default {
|
|
197
|
+
...table,
|
|
198
|
+
data: [
|
|
199
|
+
{ id: 1, name: "Product A", price: 100 },
|
|
200
|
+
{ id: 2, name: "Product B", price: 150 },
|
|
201
|
+
],
|
|
202
|
+
columns: [
|
|
203
|
+
{ id: "id", label: "ID" },
|
|
204
|
+
{ id: "name", label: "Product Name" },
|
|
205
|
+
{ id: "price", label: "Price" },
|
|
206
|
+
],
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### 2. Custom Rendering
|
|
211
|
+
|
|
212
|
+
You can customize how data is rendered in the table cells by overriding the `renderValue` method. This is useful for formatting values or displaying custom content.
|
|
213
|
+
|
|
214
|
+
The example below from `examples/apps/web-table/src/product-table/product-table.js` shows how to format values based on a `formatter` property in the column definition.
|
|
215
|
+
|
|
216
|
+
```javascript
|
|
217
|
+
import { table } from "@inglorious/web"
|
|
218
|
+
import { format } from "date-fns"
|
|
219
|
+
|
|
220
|
+
const formatters = {
|
|
221
|
+
isAvailable: (val) => (val ? "✔️" : "❌"),
|
|
222
|
+
createdAt: (val) => format(val, "dd/MM/yyyy HH:mm"),
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export const productTable = {
|
|
226
|
+
...table,
|
|
227
|
+
|
|
228
|
+
renderValue(value, column) {
|
|
229
|
+
return formatters[column.formatter]?.(value) ?? value
|
|
230
|
+
},
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### 3. Theming
|
|
235
|
+
|
|
236
|
+
The table comes with a base stylesheet (`@inglorious/web/table/base.css`) and a default theme (`@inglorious/web/table/theme.css`). You can create your own theme by creating a new CSS file and styling the table elements to match your application's design.
|
|
237
|
+
|
|
167
238
|
---
|
|
168
239
|
|
|
169
240
|
## Forms
|
|
@@ -315,17 +386,6 @@ const store = createStore({ types, entities })
|
|
|
315
386
|
|
|
316
387
|
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
388
|
|
|
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
389
|
## API Reference
|
|
330
390
|
|
|
331
391
|
**`mount(store, renderFn, element)`**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inglorious/web",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "A new web framework that leverages the power of the Inglorious Store combined with the performance and simplicity of lit-html.",
|
|
5
5
|
"author": "IceOnFire <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,7 +25,9 @@
|
|
|
25
25
|
".": {
|
|
26
26
|
"types": "./types/index.d.ts",
|
|
27
27
|
"import": "./src/index.js"
|
|
28
|
-
}
|
|
28
|
+
},
|
|
29
|
+
"./table/base.css": "./src/table/base.css",
|
|
30
|
+
"./table/theme.css": "./src/table/theme.css"
|
|
29
31
|
},
|
|
30
32
|
"files": [
|
|
31
33
|
"src",
|
package/src/index.js
CHANGED
|
@@ -2,10 +2,12 @@ export { form, getFieldError, getFieldValue, isFieldTouched } from "./form.js"
|
|
|
2
2
|
export { list } from "./list.js"
|
|
3
3
|
export { mount } from "./mount.js"
|
|
4
4
|
export { router } from "./router.js"
|
|
5
|
-
export {
|
|
5
|
+
export { table } from "./table.js"
|
|
6
6
|
export { createStore } from "@inglorious/store"
|
|
7
7
|
export { createDevtools } from "@inglorious/store/client/devtools.js"
|
|
8
8
|
export { createSelector } from "@inglorious/store/select.js"
|
|
9
9
|
export { html, render, svg } from "lit-html"
|
|
10
|
+
export { choose } from "lit-html/directives/choose.js"
|
|
10
11
|
export { classMap } from "lit-html/directives/class-map.js"
|
|
12
|
+
export { ref } from "lit-html/directives/ref.js"
|
|
11
13
|
export { repeat } from "lit-html/directives/repeat.js"
|
package/src/list.js
CHANGED
|
@@ -37,11 +37,15 @@ export const list = {
|
|
|
37
37
|
entity.visibleRange = { start, end }
|
|
38
38
|
},
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
mount(entity, containerEl) {
|
|
41
41
|
const firstItem = containerEl.querySelector("[data-index]")
|
|
42
42
|
if (!firstItem) return
|
|
43
43
|
|
|
44
44
|
entity.itemHeight = firstItem.offsetHeight
|
|
45
|
+
entity.visibleRange = {
|
|
46
|
+
start: 0,
|
|
47
|
+
end: Math.ceil(entity.viewportHeight / entity.itemHeight),
|
|
48
|
+
}
|
|
45
49
|
},
|
|
46
50
|
|
|
47
51
|
render(entity, api) {
|
|
@@ -70,7 +74,7 @@ export const list = {
|
|
|
70
74
|
${ref((el) => {
|
|
71
75
|
if (el && !itemHeight) {
|
|
72
76
|
queueMicrotask(() => {
|
|
73
|
-
api.notify(`#${entity.id}:
|
|
77
|
+
api.notify(`#${entity.id}:mount`, el)
|
|
74
78
|
})
|
|
75
79
|
}
|
|
76
80
|
})}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
.iw-table {
|
|
2
|
+
user-select: none;
|
|
3
|
+
|
|
4
|
+
.iw-table-header {
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.iw-table-header-row {
|
|
10
|
+
display: flex;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.iw-table-header-column {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.iw-table-filter-input,
|
|
19
|
+
.iw-table-filter-select {
|
|
20
|
+
width: 100%;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.iw-table-searchbar {
|
|
24
|
+
width: 100%;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.iw-table-row {
|
|
28
|
+
display: flex;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.iw-table-footer-row {
|
|
32
|
+
display: flex;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { html } from "@inglorious/web"
|
|
2
|
+
|
|
3
|
+
const INPUT_TYPE = {
|
|
4
|
+
date: "date",
|
|
5
|
+
time: "time",
|
|
6
|
+
datetime: "datetime-local",
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const dateFilter = {
|
|
10
|
+
render(entity, column, api) {
|
|
11
|
+
const filter = entity.filters[column.id] ?? {}
|
|
12
|
+
|
|
13
|
+
return html`<input
|
|
14
|
+
name=${`${column.id}Min`}
|
|
15
|
+
type=${INPUT_TYPE[column.filter.type]}
|
|
16
|
+
value=${entity.filters[column.id]}
|
|
17
|
+
@input=${(event) => {
|
|
18
|
+
const value = event.target.value
|
|
19
|
+
const formattedValue = value ? new Date(value).getTime() : null
|
|
20
|
+
|
|
21
|
+
api.notify(`#${entity.id}:filterChange`, {
|
|
22
|
+
columnId: column.id,
|
|
23
|
+
value: { ...filter, min: formattedValue },
|
|
24
|
+
})
|
|
25
|
+
}}
|
|
26
|
+
/>
|
|
27
|
+
<input
|
|
28
|
+
name=${`${column.id}Max`}
|
|
29
|
+
type=${INPUT_TYPE[column.filter.type]}
|
|
30
|
+
value=${entity.filters[column.id]}
|
|
31
|
+
@input=${(event) => {
|
|
32
|
+
const value = event.target.value
|
|
33
|
+
const formattedValue = value ? new Date(value).getTime() : null
|
|
34
|
+
|
|
35
|
+
api.notify(`#${entity.id}:filterChange`, {
|
|
36
|
+
columnId: column.id,
|
|
37
|
+
value: { ...filter, max: formattedValue },
|
|
38
|
+
})
|
|
39
|
+
}}
|
|
40
|
+
/>`
|
|
41
|
+
},
|
|
42
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { html } from "@inglorious/web"
|
|
2
|
+
|
|
3
|
+
export const numberFilter = {
|
|
4
|
+
render(entity, column, api) {
|
|
5
|
+
return html`<input
|
|
6
|
+
name=${column.id}
|
|
7
|
+
type="number"
|
|
8
|
+
placeholder=${column.filter.placeholder ?? "="}
|
|
9
|
+
value=${entity.filters[column.id]}
|
|
10
|
+
@input=${(event) => {
|
|
11
|
+
const value = event.target.value
|
|
12
|
+
const formattedValue = value ? Number(value) : null
|
|
13
|
+
|
|
14
|
+
api.notify(`#${entity.id}:filterChange`, {
|
|
15
|
+
columnId: column.id,
|
|
16
|
+
value: formattedValue,
|
|
17
|
+
})
|
|
18
|
+
}}
|
|
19
|
+
class="iw-table-cell-number"
|
|
20
|
+
/>`
|
|
21
|
+
},
|
|
22
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { html } from "@inglorious/web"
|
|
2
|
+
|
|
3
|
+
export const rangeFilter = {
|
|
4
|
+
render(entity, column, api) {
|
|
5
|
+
const filter = entity.filters[column.id] ?? {}
|
|
6
|
+
|
|
7
|
+
return html`<div class="row">
|
|
8
|
+
<input
|
|
9
|
+
name=${`${column.id}Min`}
|
|
10
|
+
type="number"
|
|
11
|
+
placeholder=${column.filter.placeholder ?? "≥"}
|
|
12
|
+
value=${filter.min}
|
|
13
|
+
@input=${(event) => {
|
|
14
|
+
const value = event.target.value
|
|
15
|
+
const formattedValue = value ? Number(value) : null
|
|
16
|
+
|
|
17
|
+
api.notify(`#${entity.id}:filterChange`, {
|
|
18
|
+
columnId: column.id,
|
|
19
|
+
value: { ...filter, min: formattedValue },
|
|
20
|
+
})
|
|
21
|
+
}}
|
|
22
|
+
class="iw-table-cell-number"
|
|
23
|
+
/>
|
|
24
|
+
<input
|
|
25
|
+
name=${`${column.id}Max`}
|
|
26
|
+
type="number"
|
|
27
|
+
placeholder=${column.filter.placeholder ?? "≤"}
|
|
28
|
+
value=${filter.max}
|
|
29
|
+
@input=${(event) => {
|
|
30
|
+
const value = event.target.value
|
|
31
|
+
const formattedValue = value ? Number(value) : null
|
|
32
|
+
|
|
33
|
+
api.notify(`#${entity.id}:filterChange`, {
|
|
34
|
+
columnId: column.id,
|
|
35
|
+
value: { ...filter, max: formattedValue },
|
|
36
|
+
})
|
|
37
|
+
}}
|
|
38
|
+
class="iw-table-cell-number"
|
|
39
|
+
/>
|
|
40
|
+
</div>`
|
|
41
|
+
},
|
|
42
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { html } from "@inglorious/web"
|
|
2
|
+
|
|
3
|
+
export const selectFilter = {
|
|
4
|
+
render(entity, column, api) {
|
|
5
|
+
return html`<select
|
|
6
|
+
name=${column.id}
|
|
7
|
+
?multiple=${column.filter.isMultiple}
|
|
8
|
+
autocomplete="off"
|
|
9
|
+
value=${entity.filters[column.id]}
|
|
10
|
+
@change=${(event) => {
|
|
11
|
+
const value = event.target.value
|
|
12
|
+
const formattedValue = value ? format(value, column.type) : null
|
|
13
|
+
|
|
14
|
+
api.notify(`#${entity.id}:filterChange`, {
|
|
15
|
+
columnId: column.id,
|
|
16
|
+
value: formattedValue,
|
|
17
|
+
})
|
|
18
|
+
}}
|
|
19
|
+
>
|
|
20
|
+
${column.filter.options.map(
|
|
21
|
+
(option) => html`<option value=${option}>${option}</option>`,
|
|
22
|
+
)}
|
|
23
|
+
</select>`
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function format(value, type) {
|
|
28
|
+
if (type === "number") return Number(value)
|
|
29
|
+
if (type === "boolean")
|
|
30
|
+
return value === "true" ? true : value === "false" ? false : null
|
|
31
|
+
if (["date", "time", "datetime"].includes(type))
|
|
32
|
+
return new Date(value).getTime()
|
|
33
|
+
return value
|
|
34
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { html } from "@inglorious/web"
|
|
2
|
+
|
|
3
|
+
export const textFilter = {
|
|
4
|
+
render(entity, column, api) {
|
|
5
|
+
return html`<input
|
|
6
|
+
name=${column.id}
|
|
7
|
+
type="text"
|
|
8
|
+
placeholder=${column.filter.placeholder ?? "Contains..."}
|
|
9
|
+
autocomplete="off"
|
|
10
|
+
value=${entity.filters[column.id]}
|
|
11
|
+
@input=${(event) => {
|
|
12
|
+
const value = event.target.value
|
|
13
|
+
const formattedValue = value || null
|
|
14
|
+
|
|
15
|
+
api.notify(`#${entity.id}:filterChange`, {
|
|
16
|
+
columnId: column.id,
|
|
17
|
+
value: formattedValue,
|
|
18
|
+
})
|
|
19
|
+
}}
|
|
20
|
+
/>`
|
|
21
|
+
},
|
|
22
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { choose, html } from "@inglorious/web"
|
|
2
|
+
|
|
3
|
+
import { dateFilter } from "./filters/date"
|
|
4
|
+
import { numberFilter } from "./filters/number"
|
|
5
|
+
import { rangeFilter } from "./filters/range"
|
|
6
|
+
import { selectFilter } from "./filters/select"
|
|
7
|
+
import { textFilter } from "./filters/text"
|
|
8
|
+
|
|
9
|
+
export const filters = {
|
|
10
|
+
render(entity, column, api) {
|
|
11
|
+
return html`${choose(
|
|
12
|
+
column.filter.type,
|
|
13
|
+
[
|
|
14
|
+
["number", () => numberFilter.render(entity, column, api)],
|
|
15
|
+
["range", () => rangeFilter.render(entity, column, api)],
|
|
16
|
+
["select", () => selectFilter.render(entity, column, api)],
|
|
17
|
+
["date", () => dateFilter.render(entity, column, api)],
|
|
18
|
+
["time", () => dateFilter.render(entity, column, api)],
|
|
19
|
+
["datetime", () => dateFilter.render(entity, column, api)],
|
|
20
|
+
],
|
|
21
|
+
() => textFilter.render(entity, column, api),
|
|
22
|
+
)}`
|
|
23
|
+
},
|
|
24
|
+
}
|