@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 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.1",
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 { getPaginationInfo, getRows, getSortDirection, table } from "./table.js"
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
- measureHeight(entity, containerEl) {
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}:measureHeight`, el)
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
+ }