@inglorious/web 2.6.0 → 2.6.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/README.md +1 -1
- package/package.json +2 -2
- package/src/form.js +14 -13
- package/src/form.test.js +25 -25
- package/src/list.js +40 -7
- package/src/list.test.js +8 -8
- package/src/router.js +5 -0
- package/src/router.test.js +6 -14
- package/src/select/logic.js +2 -12
- package/src/select.test.js +12 -12
- package/src/table/logic.js +2 -12
- package/src/table.test.js +24 -3
- package/types/form.d.ts +2 -12
- package/types/index.d.ts +1 -0
- package/types/list.d.ts +76 -0
- package/types/select.d.ts +2 -2
- package/types/table.d.ts +2 -9
package/README.md
CHANGED
|
@@ -216,7 +216,7 @@ Inglorious Web is minimal, predictable, and tiny.
|
|
|
216
216
|
|
|
217
217
|
## **HTMX / Alpine / Vanilla DOM**
|
|
218
218
|
|
|
219
|
-
|
|
219
|
+
Inglorious Web is closer philosophically to **HTMX** and **vanilla JS**, but with a declarative rendering model and entity-based state.
|
|
220
220
|
|
|
221
221
|
---
|
|
222
222
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inglorious/web",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.1",
|
|
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",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"lit-html": "^3.3.1",
|
|
43
|
-
"@inglorious/store": "
|
|
43
|
+
"@inglorious/store": "8.0.0",
|
|
44
44
|
"@inglorious/utils": "3.7.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
package/src/form.js
CHANGED
|
@@ -26,6 +26,8 @@ import { clone, get, set } from "@inglorious/utils/data-structures/object.js"
|
|
|
26
26
|
const NO_ITEMS_REMOVED = 0
|
|
27
27
|
const ONE_ITEM_REMOVED = 1
|
|
28
28
|
|
|
29
|
+
let areListenersInitialized = false
|
|
30
|
+
|
|
29
31
|
/**
|
|
30
32
|
* A type definition for managing form state within an entity-based system.
|
|
31
33
|
* It handles initialization, field changes, validation, and submission.
|
|
@@ -37,33 +39,32 @@ export const form = {
|
|
|
37
39
|
* Initializes the form entity by resetting it to its initial state.
|
|
38
40
|
* @param {FormEntity} entity - The form entity.
|
|
39
41
|
*/
|
|
40
|
-
init(
|
|
41
|
-
|
|
42
|
+
init() {
|
|
43
|
+
if (areListenersInitialized) return
|
|
42
44
|
|
|
43
|
-
document.addEventListener("click", (
|
|
44
|
-
const button =
|
|
45
|
+
document.addEventListener("click", (event) => {
|
|
46
|
+
const button = event.target.closest("button")
|
|
45
47
|
|
|
46
48
|
if (!button || button.getAttribute("type") === "submit") return
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
event.preventDefault()
|
|
49
51
|
})
|
|
50
52
|
|
|
51
|
-
document.addEventListener("submit", (
|
|
52
|
-
const form =
|
|
53
|
+
document.addEventListener("submit", (event) => {
|
|
54
|
+
const form = event.target.closest("form")
|
|
53
55
|
|
|
54
56
|
if (!form) return
|
|
55
|
-
|
|
56
|
-
e.preventDefault()
|
|
57
|
+
event.preventDefault()
|
|
57
58
|
})
|
|
59
|
+
|
|
60
|
+
areListenersInitialized = true
|
|
58
61
|
},
|
|
59
62
|
|
|
60
63
|
/**
|
|
61
|
-
* Resets the form entity
|
|
64
|
+
* Resets the form entity with default state.
|
|
62
65
|
* @param {FormEntity} entity - The form entity.
|
|
63
|
-
* @param {string|number} entityId - The ID from the create event.
|
|
64
66
|
*/
|
|
65
|
-
create(entity
|
|
66
|
-
if (id !== entity.id) return
|
|
67
|
+
create(entity) {
|
|
67
68
|
resetForm(entity)
|
|
68
69
|
},
|
|
69
70
|
|
package/src/form.test.js
CHANGED
|
@@ -22,12 +22,28 @@ describe("form", () => {
|
|
|
22
22
|
})
|
|
23
23
|
|
|
24
24
|
describe("init()", () => {
|
|
25
|
+
it("should add global event listeners for click and submit", () => {
|
|
26
|
+
const spy = vi.spyOn(document, "addEventListener")
|
|
27
|
+
form.init(entity)
|
|
28
|
+
expect(spy).toHaveBeenCalledWith("click", expect.any(Function))
|
|
29
|
+
expect(spy).toHaveBeenCalledWith("submit", expect.any(Function))
|
|
30
|
+
spy.mockRestore()
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe("create()", () => {
|
|
35
|
+
it("should initialize the form", () => {
|
|
36
|
+
entity.values = {}
|
|
37
|
+
form.create(entity)
|
|
38
|
+
expect(entity.values).toEqual(entity.initialValues)
|
|
39
|
+
})
|
|
40
|
+
|
|
25
41
|
it("should reset the form to its initial state", () => {
|
|
26
42
|
// mess up the state first
|
|
27
43
|
entity.values = {}
|
|
28
44
|
entity.isPristine = false
|
|
29
45
|
|
|
30
|
-
form.
|
|
46
|
+
form.create(entity)
|
|
31
47
|
|
|
32
48
|
expect(entity.values).toEqual(entity.initialValues)
|
|
33
49
|
expect(entity.values).not.toBe(entity.initialValues) // should be a clone
|
|
@@ -44,27 +60,11 @@ describe("form", () => {
|
|
|
44
60
|
tags: [null, null],
|
|
45
61
|
})
|
|
46
62
|
})
|
|
47
|
-
|
|
48
|
-
it("should add global event listeners for click and submit", () => {
|
|
49
|
-
const spy = vi.spyOn(document, "addEventListener")
|
|
50
|
-
form.init(entity)
|
|
51
|
-
expect(spy).toHaveBeenCalledWith("click", expect.any(Function))
|
|
52
|
-
expect(spy).toHaveBeenCalledWith("submit", expect.any(Function))
|
|
53
|
-
spy.mockRestore()
|
|
54
|
-
})
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
describe("create()", () => {
|
|
58
|
-
it("should reset the form", () => {
|
|
59
|
-
entity.values = {}
|
|
60
|
-
form.create(entity, "test-form")
|
|
61
|
-
expect(entity.values).toEqual(entity.initialValues)
|
|
62
|
-
})
|
|
63
63
|
})
|
|
64
64
|
|
|
65
65
|
describe("reset()", () => {
|
|
66
66
|
it("should reset the form to its initial state", () => {
|
|
67
|
-
form.create(entity
|
|
67
|
+
form.create(entity)
|
|
68
68
|
entity.values.name = "Jane Doe"
|
|
69
69
|
entity.isPristine = false
|
|
70
70
|
|
|
@@ -77,7 +77,7 @@ describe("form", () => {
|
|
|
77
77
|
|
|
78
78
|
describe("fieldChange()", () => {
|
|
79
79
|
beforeEach(() => {
|
|
80
|
-
form.create(entity
|
|
80
|
+
form.create(entity)
|
|
81
81
|
})
|
|
82
82
|
|
|
83
83
|
it("should update a field's value, mark it as touched, and set form to dirty", () => {
|
|
@@ -124,7 +124,7 @@ describe("form", () => {
|
|
|
124
124
|
|
|
125
125
|
describe("fieldBlur()", () => {
|
|
126
126
|
beforeEach(() => {
|
|
127
|
-
form.create(entity
|
|
127
|
+
form.create(entity)
|
|
128
128
|
})
|
|
129
129
|
|
|
130
130
|
it("should mark a field as touched", () => {
|
|
@@ -144,7 +144,7 @@ describe("form", () => {
|
|
|
144
144
|
|
|
145
145
|
describe("field array operations", () => {
|
|
146
146
|
beforeEach(() => {
|
|
147
|
-
form.create(entity
|
|
147
|
+
form.create(entity)
|
|
148
148
|
})
|
|
149
149
|
|
|
150
150
|
it("fieldArrayAppend: should append a value and metadata", () => {
|
|
@@ -198,7 +198,7 @@ describe("form", () => {
|
|
|
198
198
|
|
|
199
199
|
describe("validate()", () => {
|
|
200
200
|
beforeEach(() => {
|
|
201
|
-
form.create(entity
|
|
201
|
+
form.create(entity)
|
|
202
202
|
})
|
|
203
203
|
|
|
204
204
|
it("should set errors and isValid based on the validation function", () => {
|
|
@@ -231,7 +231,7 @@ describe("form", () => {
|
|
|
231
231
|
let api
|
|
232
232
|
|
|
233
233
|
beforeEach(() => {
|
|
234
|
-
form.create(entity
|
|
234
|
+
form.create(entity)
|
|
235
235
|
api = { notify: vi.fn() }
|
|
236
236
|
})
|
|
237
237
|
|
|
@@ -271,7 +271,7 @@ describe("form", () => {
|
|
|
271
271
|
|
|
272
272
|
describe("validationComplete()", () => {
|
|
273
273
|
it("should update form state after async validation", () => {
|
|
274
|
-
form.create(entity
|
|
274
|
+
form.create(entity)
|
|
275
275
|
entity.isValidating = true
|
|
276
276
|
const errors = { name: "Error" }
|
|
277
277
|
|
|
@@ -285,7 +285,7 @@ describe("form", () => {
|
|
|
285
285
|
|
|
286
286
|
describe("validationError()", () => {
|
|
287
287
|
it("should update form state after an async validation error", () => {
|
|
288
|
-
form.create(entity
|
|
288
|
+
form.create(entity)
|
|
289
289
|
entity.isValidating = true
|
|
290
290
|
|
|
291
291
|
form.validationError(entity, { error: "Network Error" })
|
package/src/list.js
CHANGED
|
@@ -5,16 +5,26 @@ import { styleMap } from "lit-html/directives/style-map.js"
|
|
|
5
5
|
const LIST_START = 0
|
|
6
6
|
const PRETTY_INDEX = 1
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {import('../types/list').ListEntity} ListEntity
|
|
10
|
+
* @typedef {import('../types/mount').Api} Api
|
|
11
|
+
* @typedef {import('lit-html').TemplateResult} TemplateResult
|
|
12
|
+
*/
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
export const list = {
|
|
15
|
+
/**
|
|
16
|
+
* Initializes the list entity with default state.
|
|
17
|
+
* @param {ListEntity} entity
|
|
18
|
+
*/
|
|
19
|
+
create(entity) {
|
|
15
20
|
resetList(entity)
|
|
16
21
|
},
|
|
17
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Handles the scroll event to update the visible range.
|
|
25
|
+
* @param {ListEntity} entity
|
|
26
|
+
* @param {HTMLElement} containerEl
|
|
27
|
+
*/
|
|
18
28
|
scroll(entity, containerEl) {
|
|
19
29
|
const scrollTop = containerEl.scrollTop
|
|
20
30
|
const { items, bufferSize, itemHeight, estimatedHeight, viewportHeight } =
|
|
@@ -39,6 +49,11 @@ export const list = {
|
|
|
39
49
|
entity.visibleRange = { start, end }
|
|
40
50
|
},
|
|
41
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Mounts the list, measuring the first item to determine item height.
|
|
54
|
+
* @param {ListEntity} entity
|
|
55
|
+
* @param {HTMLElement} containerEl
|
|
56
|
+
*/
|
|
42
57
|
mount(entity, containerEl) {
|
|
43
58
|
const firstItem = containerEl.querySelector("[data-index]")
|
|
44
59
|
if (!firstItem) return
|
|
@@ -50,6 +65,12 @@ export const list = {
|
|
|
50
65
|
}
|
|
51
66
|
},
|
|
52
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Renders the virtualized list component.
|
|
70
|
+
* @param {ListEntity} entity
|
|
71
|
+
* @param {Api} api
|
|
72
|
+
* @returns {TemplateResult}
|
|
73
|
+
*/
|
|
53
74
|
render(entity, api) {
|
|
54
75
|
const { items, visibleRange, viewportHeight, itemHeight, estimatedHeight } =
|
|
55
76
|
entity
|
|
@@ -109,11 +130,23 @@ export const list = {
|
|
|
109
130
|
`
|
|
110
131
|
},
|
|
111
132
|
|
|
112
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Default item renderer.
|
|
135
|
+
* @param {any} item
|
|
136
|
+
* @param {number} index
|
|
137
|
+
* @param {Api} api
|
|
138
|
+
* @returns {TemplateResult}
|
|
139
|
+
*/
|
|
140
|
+
// eslint-disable-next-line no-unused-vars
|
|
141
|
+
renderItem(item, index, api) {
|
|
113
142
|
return html`<div>${index + PRETTY_INDEX}. ${JSON.stringify(item)}</div>`
|
|
114
143
|
},
|
|
115
144
|
}
|
|
116
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Resets the list entity state.
|
|
148
|
+
* @param {ListEntity} entity
|
|
149
|
+
*/
|
|
117
150
|
function resetList(entity) {
|
|
118
151
|
entity.scrollTop = 0
|
|
119
152
|
entity.visibleRange ??= { start: 0, end: 20 }
|
package/src/list.test.js
CHANGED
|
@@ -20,9 +20,9 @@ describe("list", () => {
|
|
|
20
20
|
}
|
|
21
21
|
})
|
|
22
22
|
|
|
23
|
-
describe("
|
|
23
|
+
describe("and create()", () => {
|
|
24
24
|
it("should set default list properties on init", () => {
|
|
25
|
-
list.
|
|
25
|
+
list.create(entity)
|
|
26
26
|
expect(entity.scrollTop).toBe(0)
|
|
27
27
|
expect(entity.visibleRange).toEqual({ start: 0, end: 20 })
|
|
28
28
|
expect(entity.viewportHeight).toBe(600)
|
|
@@ -35,7 +35,7 @@ describe("list", () => {
|
|
|
35
35
|
entity.viewportHeight = 800
|
|
36
36
|
entity.visibleRange = { start: 10, end: 30 }
|
|
37
37
|
|
|
38
|
-
list.
|
|
38
|
+
list.create(entity)
|
|
39
39
|
|
|
40
40
|
expect(entity.viewportHeight).toBe(800)
|
|
41
41
|
expect(entity.visibleRange).toEqual({ start: 10, end: 30 })
|
|
@@ -43,7 +43,7 @@ describe("list", () => {
|
|
|
43
43
|
})
|
|
44
44
|
|
|
45
45
|
it("should reset the list on create", () => {
|
|
46
|
-
list.create(entity
|
|
46
|
+
list.create(entity)
|
|
47
47
|
expect(entity.scrollTop).toBe(0)
|
|
48
48
|
expect(entity.visibleRange).toEqual({ start: 0, end: 20 })
|
|
49
49
|
})
|
|
@@ -51,7 +51,7 @@ describe("list", () => {
|
|
|
51
51
|
|
|
52
52
|
describe("scroll()", () => {
|
|
53
53
|
beforeEach(() => {
|
|
54
|
-
list.
|
|
54
|
+
list.create(entity)
|
|
55
55
|
})
|
|
56
56
|
|
|
57
57
|
it("should calculate visible range based on itemHeight", () => {
|
|
@@ -116,7 +116,7 @@ describe("list", () => {
|
|
|
116
116
|
|
|
117
117
|
describe("mount()", () => {
|
|
118
118
|
it("should measure and set itemHeight and update visibleRange", () => {
|
|
119
|
-
list.
|
|
119
|
+
list.create(entity)
|
|
120
120
|
const itemEl = document.createElement("div")
|
|
121
121
|
vi.spyOn(itemEl, "offsetHeight", "get").mockReturnValue(40)
|
|
122
122
|
|
|
@@ -133,7 +133,7 @@ describe("list", () => {
|
|
|
133
133
|
})
|
|
134
134
|
|
|
135
135
|
it("should do nothing if no item element is found", () => {
|
|
136
|
-
list.
|
|
136
|
+
list.create(entity)
|
|
137
137
|
const originalEntity = { ...entity }
|
|
138
138
|
const containerEl = {
|
|
139
139
|
querySelector: vi.fn().mockReturnValue(null),
|
|
@@ -149,7 +149,7 @@ describe("list", () => {
|
|
|
149
149
|
let api
|
|
150
150
|
|
|
151
151
|
beforeEach(() => {
|
|
152
|
-
list.
|
|
152
|
+
list.create(entity)
|
|
153
153
|
api = {
|
|
154
154
|
notify: vi.fn(),
|
|
155
155
|
getType: vi.fn().mockReturnValue({
|
package/src/router.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
const SKIP_FULL_MATCH_GROUP = 1 // .match() result at index 0 is the full string
|
|
8
8
|
const REMOVE_COLON_PREFIX = 1
|
|
9
9
|
|
|
10
|
+
let areListenersInitialized = false
|
|
11
|
+
|
|
10
12
|
/**
|
|
11
13
|
* Client-side router for entity-based systems. Handles URL changes, link interception, and browser history management.
|
|
12
14
|
* @type {RouterType}
|
|
@@ -34,6 +36,9 @@ export const router = {
|
|
|
34
36
|
})
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
if (areListenersInitialized) return
|
|
40
|
+
areListenersInitialized = true
|
|
41
|
+
|
|
37
42
|
// Listen for browser back/forward
|
|
38
43
|
window.addEventListener("popstate", () => {
|
|
39
44
|
const path = window.location.pathname + window.location.search
|
package/src/router.test.js
CHANGED
|
@@ -44,33 +44,25 @@ describe("router", () => {
|
|
|
44
44
|
})
|
|
45
45
|
|
|
46
46
|
describe("init()", () => {
|
|
47
|
-
it("should initialize with the current window.location", () => {
|
|
47
|
+
it("should initialize with the current window.location, set up a popstate listener, and set up a click listener for link interception", () => {
|
|
48
48
|
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
49
49
|
pathname: "/users/123",
|
|
50
50
|
search: "?sort=asc",
|
|
51
51
|
hash: "#details",
|
|
52
52
|
origin: "http://localhost:3000",
|
|
53
53
|
})
|
|
54
|
+
const windowSpy = vi.spyOn(window, "addEventListener")
|
|
55
|
+
const documentSpy = vi.spyOn(document, "addEventListener")
|
|
54
56
|
|
|
55
|
-
router.init(entity,
|
|
57
|
+
router.init(entity, undefined, api)
|
|
56
58
|
|
|
57
59
|
expect(api.notify).toHaveBeenCalledWith("#router:navigate", {
|
|
58
60
|
to: "/users/123?sort=asc",
|
|
59
61
|
params: { id: "123" },
|
|
60
62
|
replace: true,
|
|
61
63
|
})
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
it("should set up a popstate listener", () => {
|
|
65
|
-
const spy = vi.spyOn(window, "addEventListener")
|
|
66
|
-
router.init(entity, {}, api)
|
|
67
|
-
expect(spy).toHaveBeenCalledWith("popstate", expect.any(Function))
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it("should set up a click listener for link interception", () => {
|
|
71
|
-
const spy = vi.spyOn(document, "addEventListener")
|
|
72
|
-
router.init(entity, {}, api)
|
|
73
|
-
expect(spy).toHaveBeenCalledWith("click", expect.any(Function))
|
|
64
|
+
expect(windowSpy).toHaveBeenCalledWith("popstate", expect.any(Function))
|
|
65
|
+
expect(documentSpy).toHaveBeenCalledWith("click", expect.any(Function))
|
|
74
66
|
})
|
|
75
67
|
})
|
|
76
68
|
|
package/src/select/logic.js
CHANGED
|
@@ -7,20 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
export const logic = {
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Resets the select entity with default state.
|
|
11
11
|
* @param {SelectEntity} entity
|
|
12
12
|
*/
|
|
13
|
-
|
|
14
|
-
initSelect(entity)
|
|
15
|
-
},
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Resets the select entity when a 'create' event payload matches its ID.
|
|
19
|
-
* @param {SelectEntity} entity
|
|
20
|
-
* @param {string|number} id
|
|
21
|
-
*/
|
|
22
|
-
create(entity, id) {
|
|
23
|
-
if (id !== entity.id) return
|
|
13
|
+
create(entity) {
|
|
24
14
|
initSelect(entity)
|
|
25
15
|
},
|
|
26
16
|
|
package/src/select.test.js
CHANGED
|
@@ -35,9 +35,9 @@ describe("select", () => {
|
|
|
35
35
|
})
|
|
36
36
|
|
|
37
37
|
describe("logic", () => {
|
|
38
|
-
describe("
|
|
38
|
+
describe("create()", () => {
|
|
39
39
|
it("should initialize with default state", () => {
|
|
40
|
-
select.
|
|
40
|
+
select.create(entity)
|
|
41
41
|
|
|
42
42
|
expect(entity.isOpen).toBe(false)
|
|
43
43
|
expect(entity.searchTerm).toBe("")
|
|
@@ -58,14 +58,14 @@ describe("select", () => {
|
|
|
58
58
|
|
|
59
59
|
it("should initialize multi-select with empty array", () => {
|
|
60
60
|
entity.isMulti = true
|
|
61
|
-
select.
|
|
61
|
+
select.create(entity)
|
|
62
62
|
expect(entity.selectedValue).toEqual([])
|
|
63
63
|
})
|
|
64
64
|
})
|
|
65
65
|
|
|
66
66
|
describe("open() and close()", () => {
|
|
67
67
|
beforeEach(() => {
|
|
68
|
-
select.
|
|
68
|
+
select.create(entity)
|
|
69
69
|
})
|
|
70
70
|
|
|
71
71
|
it("open: should open the dropdown", () => {
|
|
@@ -101,7 +101,7 @@ describe("select", () => {
|
|
|
101
101
|
|
|
102
102
|
describe("toggle()", () => {
|
|
103
103
|
beforeEach(() => {
|
|
104
|
-
select.
|
|
104
|
+
select.create(entity)
|
|
105
105
|
})
|
|
106
106
|
|
|
107
107
|
it("should open if closed", () => {
|
|
@@ -118,7 +118,7 @@ describe("select", () => {
|
|
|
118
118
|
|
|
119
119
|
describe("optionSelect()", () => {
|
|
120
120
|
beforeEach(() => {
|
|
121
|
-
select.
|
|
121
|
+
select.create(entity)
|
|
122
122
|
})
|
|
123
123
|
|
|
124
124
|
it("should select an option in single-select mode", () => {
|
|
@@ -130,7 +130,7 @@ describe("select", () => {
|
|
|
130
130
|
|
|
131
131
|
it("should add option in multi-select mode", () => {
|
|
132
132
|
entity.isMulti = true
|
|
133
|
-
select.
|
|
133
|
+
select.create(entity)
|
|
134
134
|
entity.isOpen = true // Open dropdown first
|
|
135
135
|
const option = sampleOptions[0]
|
|
136
136
|
select.optionSelect(entity, option)
|
|
@@ -140,7 +140,7 @@ describe("select", () => {
|
|
|
140
140
|
|
|
141
141
|
it("should remove option in multi-select mode if already selected", () => {
|
|
142
142
|
entity.isMulti = true
|
|
143
|
-
select.
|
|
143
|
+
select.create(entity)
|
|
144
144
|
const option = sampleOptions[0]
|
|
145
145
|
select.optionSelect(entity, option) // Add
|
|
146
146
|
select.optionSelect(entity, option) // Remove
|
|
@@ -156,7 +156,7 @@ describe("select", () => {
|
|
|
156
156
|
|
|
157
157
|
describe("clear()", () => {
|
|
158
158
|
beforeEach(() => {
|
|
159
|
-
select.
|
|
159
|
+
select.create(entity)
|
|
160
160
|
})
|
|
161
161
|
|
|
162
162
|
it("should clear selection in single-select mode", () => {
|
|
@@ -167,7 +167,7 @@ describe("select", () => {
|
|
|
167
167
|
|
|
168
168
|
it("should clear selection in multi-select mode", () => {
|
|
169
169
|
entity.isMulti = true
|
|
170
|
-
select.
|
|
170
|
+
select.create(entity)
|
|
171
171
|
entity.selectedValue = ["br", "us"]
|
|
172
172
|
select.clear(entity)
|
|
173
173
|
expect(entity.selectedValue).toEqual([])
|
|
@@ -183,7 +183,7 @@ describe("select", () => {
|
|
|
183
183
|
|
|
184
184
|
describe("searchChange()", () => {
|
|
185
185
|
beforeEach(() => {
|
|
186
|
-
select.
|
|
186
|
+
select.create(entity)
|
|
187
187
|
})
|
|
188
188
|
|
|
189
189
|
it("should update searchTerm and filter options", () => {
|
|
@@ -211,7 +211,7 @@ describe("select", () => {
|
|
|
211
211
|
|
|
212
212
|
describe("keyboard navigation", () => {
|
|
213
213
|
beforeEach(() => {
|
|
214
|
-
select.
|
|
214
|
+
select.create(entity)
|
|
215
215
|
})
|
|
216
216
|
|
|
217
217
|
it("focusNext: should move to next option", () => {
|
package/src/table/logic.js
CHANGED
|
@@ -7,20 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
export const logic = {
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Resets the table entity with default state.
|
|
11
11
|
* @param {TableEntity} entity
|
|
12
12
|
*/
|
|
13
|
-
|
|
14
|
-
initTable(entity)
|
|
15
|
-
},
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Resets the table entity when a 'create' event payload matches its ID.
|
|
19
|
-
* @param {TableEntity} entity
|
|
20
|
-
* @param {string|number} id
|
|
21
|
-
*/
|
|
22
|
-
create(entity, id) {
|
|
23
|
-
if (id !== entity.id) return
|
|
13
|
+
create(entity) {
|
|
24
14
|
initTable(entity)
|
|
25
15
|
},
|
|
26
16
|
|
package/src/table.test.js
CHANGED
|
@@ -61,14 +61,13 @@ describe("table", () => {
|
|
|
61
61
|
pagination: { page: 0, pageSize: 2 },
|
|
62
62
|
search: { value: "" },
|
|
63
63
|
}
|
|
64
|
-
table.init(entity)
|
|
65
64
|
})
|
|
66
65
|
|
|
67
66
|
describe("logic", () => {
|
|
68
|
-
describe("
|
|
67
|
+
describe("create()", () => {
|
|
69
68
|
it("should initialize with default state", () => {
|
|
70
69
|
const newEntity = { data: [{ id: 1, name: "Test" }] }
|
|
71
|
-
table.
|
|
70
|
+
table.create(newEntity)
|
|
72
71
|
expect(newEntity.sorts).toEqual([])
|
|
73
72
|
expect(newEntity.filters).toEqual({})
|
|
74
73
|
expect(newEntity.selection).toEqual([])
|
|
@@ -79,6 +78,10 @@ describe("table", () => {
|
|
|
79
78
|
})
|
|
80
79
|
|
|
81
80
|
describe("sortChange()", () => {
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
table.create(entity)
|
|
83
|
+
})
|
|
84
|
+
|
|
82
85
|
it("should add a new sort", () => {
|
|
83
86
|
table.sortChange(entity, "name")
|
|
84
87
|
expect(entity.sorts).toEqual([{ column: "name", direction: "asc" }])
|
|
@@ -105,6 +108,10 @@ describe("table", () => {
|
|
|
105
108
|
})
|
|
106
109
|
|
|
107
110
|
describe("filterChange()", () => {
|
|
111
|
+
beforeEach(() => {
|
|
112
|
+
table.create(entity)
|
|
113
|
+
})
|
|
114
|
+
|
|
108
115
|
it("should add a filter", () => {
|
|
109
116
|
table.filterChange(entity, { columnId: "name", value: "Alice" })
|
|
110
117
|
expect(entity.filters.name).toBe("Alice")
|
|
@@ -124,6 +131,10 @@ describe("table", () => {
|
|
|
124
131
|
})
|
|
125
132
|
|
|
126
133
|
describe("pagination", () => {
|
|
134
|
+
beforeEach(() => {
|
|
135
|
+
table.create(entity)
|
|
136
|
+
})
|
|
137
|
+
|
|
127
138
|
it("pageNext: should go to the next page", () => {
|
|
128
139
|
table.pageNext(entity)
|
|
129
140
|
expect(entity.pagination.page).toBe(1)
|
|
@@ -155,6 +166,10 @@ describe("table", () => {
|
|
|
155
166
|
})
|
|
156
167
|
|
|
157
168
|
describe("selection", () => {
|
|
169
|
+
beforeEach(() => {
|
|
170
|
+
table.create(entity)
|
|
171
|
+
})
|
|
172
|
+
|
|
158
173
|
it("rowToggle: should select an unselected row", () => {
|
|
159
174
|
table.rowToggle(entity, 1)
|
|
160
175
|
expect(entity.selection).toContain(1)
|
|
@@ -189,6 +204,10 @@ describe("table", () => {
|
|
|
189
204
|
})
|
|
190
205
|
|
|
191
206
|
describe("getters and selectors", () => {
|
|
207
|
+
beforeEach(() => {
|
|
208
|
+
table.create(entity)
|
|
209
|
+
})
|
|
210
|
+
|
|
192
211
|
it("getRows: should return sorted, filtered, and paginated rows", () => {
|
|
193
212
|
// Sort by age descending
|
|
194
213
|
table.sortChange(entity, "age")
|
|
@@ -256,6 +275,8 @@ describe("table", () => {
|
|
|
256
275
|
renderFooter: vi.fn(() => html`<div>Footer</div>`),
|
|
257
276
|
}),
|
|
258
277
|
}
|
|
278
|
+
|
|
279
|
+
table.create(entity)
|
|
259
280
|
})
|
|
260
281
|
|
|
261
282
|
it("render: should call sub-renderers", () => {
|
package/types/form.d.ts
CHANGED
|
@@ -191,20 +191,10 @@ export interface FormValidationErrorPayload {
|
|
|
191
191
|
*/
|
|
192
192
|
export declare const form: {
|
|
193
193
|
/**
|
|
194
|
-
* Initializes the form entity
|
|
194
|
+
* Initializes the form entity with default state.
|
|
195
195
|
* @param entity The form entity.
|
|
196
196
|
*/
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Resets the form entity when a 'create' event payload matches its ID.
|
|
201
|
-
* @param entity The form entity.
|
|
202
|
-
* @param entityId The entity ID from the create event, used to target a specific form.
|
|
203
|
-
*/
|
|
204
|
-
create<T extends FormValues>(
|
|
205
|
-
entity: FormEntity<T>,
|
|
206
|
-
entityId: string | number,
|
|
207
|
-
): void
|
|
197
|
+
create<T extends FormValues>(entity: FormEntity<T>): void
|
|
208
198
|
|
|
209
199
|
/**
|
|
210
200
|
* Appends an item to a field array.
|
package/types/index.d.ts
CHANGED
package/types/list.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { TemplateResult } from "lit-html"
|
|
2
|
+
import type { Api } from "./mount"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Represents the visible range of items in the list.
|
|
6
|
+
*/
|
|
7
|
+
export interface VisibleRange {
|
|
8
|
+
start: number
|
|
9
|
+
end: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Represents the state of a list entity.
|
|
14
|
+
*/
|
|
15
|
+
export interface ListEntity<T = any> {
|
|
16
|
+
/** A unique identifier for the list entity. */
|
|
17
|
+
id: string | number
|
|
18
|
+
/** The entity type (usually 'list'). */
|
|
19
|
+
type: string
|
|
20
|
+
/** The array of items to render. */
|
|
21
|
+
items: T[]
|
|
22
|
+
/** The current scroll position of the list container. */
|
|
23
|
+
scrollTop: number
|
|
24
|
+
/** The range of items currently visible (plus buffer). */
|
|
25
|
+
visibleRange: VisibleRange
|
|
26
|
+
/** The height of the viewport in pixels. */
|
|
27
|
+
viewportHeight: number
|
|
28
|
+
/** The number of extra items to render above and below the visible range. */
|
|
29
|
+
bufferSize: number
|
|
30
|
+
/** The fixed height of each item in pixels, or null if measuring. */
|
|
31
|
+
itemHeight: number | null
|
|
32
|
+
/** The estimated height of an item, used before measurement. */
|
|
33
|
+
estimatedHeight: number
|
|
34
|
+
/** Any other custom properties. */
|
|
35
|
+
[key: string]: any
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The list type implementation.
|
|
40
|
+
*/
|
|
41
|
+
export declare const list: {
|
|
42
|
+
/**
|
|
43
|
+
* Initializes the list entity with default state.
|
|
44
|
+
* @param entity The list entity.
|
|
45
|
+
*/
|
|
46
|
+
create(entity: ListEntity): void
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Handles the scroll event to update the visible range.
|
|
50
|
+
* @param entity The list entity.
|
|
51
|
+
* @param containerEl The scrolling container element.
|
|
52
|
+
*/
|
|
53
|
+
scroll(entity: ListEntity, containerEl: HTMLElement): void
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Mounts the list, measuring the first item to determine item height.
|
|
57
|
+
* @param entity The list entity.
|
|
58
|
+
* @param containerEl The scrolling container element.
|
|
59
|
+
*/
|
|
60
|
+
mount(entity: ListEntity, containerEl: HTMLElement): void
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Renders the virtualized list component.
|
|
64
|
+
* @param entity The list entity.
|
|
65
|
+
* @param api The store API.
|
|
66
|
+
*/
|
|
67
|
+
render(entity: ListEntity, api: Api): TemplateResult
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Default item renderer.
|
|
71
|
+
* @param item The item to render.
|
|
72
|
+
* @param index The index of the item.
|
|
73
|
+
* @param api The store API.
|
|
74
|
+
*/
|
|
75
|
+
renderItem(item: any, index: number, api: Api): TemplateResult
|
|
76
|
+
}
|
package/types/select.d.ts
CHANGED
|
@@ -61,9 +61,9 @@ export interface SelectEntity {
|
|
|
61
61
|
export declare const select: {
|
|
62
62
|
/**
|
|
63
63
|
* Initializes the select entity with default state.
|
|
64
|
-
* @param
|
|
64
|
+
* @param {SelectEntity} entity
|
|
65
65
|
*/
|
|
66
|
-
|
|
66
|
+
create(entity: SelectEntity): void
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
69
|
* Renders the select component.
|
package/types/table.d.ts
CHANGED
|
@@ -68,17 +68,10 @@ export interface TableEntity<T = any> {
|
|
|
68
68
|
*/
|
|
69
69
|
export declare const table: {
|
|
70
70
|
/**
|
|
71
|
-
* Initializes the table entity.
|
|
71
|
+
* Initializes the table entity with default state.
|
|
72
72
|
* @param entity The table entity.
|
|
73
73
|
*/
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Resets the table entity when a 'create' event payload matches its ID.
|
|
78
|
-
* @param entity The table entity.
|
|
79
|
-
* @param id The ID of the entity to create/reset.
|
|
80
|
-
*/
|
|
81
|
-
create(entity: TableEntity, id: string): void
|
|
74
|
+
create(entity: TableEntity): void
|
|
82
75
|
|
|
83
76
|
/**
|
|
84
77
|
* Toggles sorting for a specific column.
|