@inglorious/web 3.0.0 → 3.0.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 CHANGED
@@ -371,7 +371,8 @@ To enable the router, add it to your store's types and create a `router` entity.
371
371
 
372
372
  ```javascript
373
373
  // store.js
374
- import { createStore, html, router } from "@inglorious/web"
374
+ import { createStore, html } from "@inglorious/web"
375
+ import { router } from "@inglorious/web/router"
375
376
 
376
377
  const types = {
377
378
  // 1. Add the router type to your store's types
@@ -520,7 +521,8 @@ export const requireAuth = (type) => ({
520
521
 
521
522
  ```javascript
522
523
  // store.js
523
- import { createStore, router } from "@inglorious/web"
524
+ import { createStore } from "@inglorious/web"
525
+ import { router } from "@inglorious/web/router"
524
526
  import { requireAuth } from "./guards/require-auth.js"
525
527
  import { adminPage } from "./pages/admin.js"
526
528
  import { loginPage } from "./pages/login.js"
@@ -646,7 +648,7 @@ To use it, import the `table` type and its CSS, then create an entity for your t
646
648
 
647
649
  ```javascript
648
650
  // In your entity definition file
649
- import { table } from "@inglorious/web"
651
+ import { table } from "@inglorious/web/table"
650
652
 
651
653
  // Import base styles and a theme. You can create your own theme.
652
654
  import "@inglorious/web/table/base.css"
@@ -673,7 +675,7 @@ You can customize how data is rendered in the table cells by overriding the `ren
673
675
  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.
674
676
 
675
677
  ```javascript
676
- import { table } from "@inglorious/web"
678
+ import { table } from "@inglorious/web/table"
677
679
  import { format } from "date-fns"
678
680
 
679
681
  const formatters = {
@@ -705,7 +707,8 @@ The table comes with a base stylesheet (`@inglorious/web/table/base.css`) and a
705
707
  Import the `select` type and its CSS, then create an entity.
706
708
 
707
709
  ```javascript
708
- import { createStore, select } from "@inglorious/web"
710
+ import { createStore } from "@inglorious/web"
711
+ import { select } from "@inglorious/web/select"
709
712
  // Import base styles and theme
710
713
  import "@inglorious/web/select/base.css"
711
714
  import "@inglorious/web/select/theme.css"
@@ -765,7 +768,8 @@ It listens to internal events like `#<id>:toggle`, `#<id>:optionSelect`, etc. Yo
765
768
  Include `form` in your `types` and create an entity for the form (use any id you like — `form` is used below for clarity):
766
769
 
767
770
  ```javascript
768
- import { createStore, form } from "@inglorious/web"
771
+ import { createStore } from "@inglorious/web"
772
+ import { form } from "@inglorious/web/form"
769
773
 
770
774
  const types = { form }
771
775
 
@@ -866,7 +870,8 @@ It also expects the item type to export `renderItem(item, index, api)` so each v
866
870
  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.
867
871
 
868
872
  ```javascript
869
- import { createStore, html, list } from "@inglorious/web"
873
+ import { createStore, html } from "@inglorious/web"
874
+ import { list } from "@inglorious/web/list"
870
875
 
871
876
  // Extend the built-in list type to render product items
872
877
  const productList = {
@@ -952,18 +957,19 @@ import {
952
957
  styleMap,
953
958
  unsafeHTML,
954
959
  when,
955
- // router stuff
956
- router,
957
- // table stuff
958
- table,
959
- // form stuff
960
+ } from "@inglorious/web"
961
+
962
+ // Subpath imports for tree-shaking
963
+ import {
960
964
  form,
961
965
  getFieldError,
962
966
  getFieldValue,
963
967
  isFieldTouched,
964
- // virtualized list stuff
965
- list,
966
- } from "@inglorious/web"
968
+ } from "@inglorious/web/form"
969
+ import { list } from "@inglorious/web/list"
970
+ import { router } from "@inglorious/web/router"
971
+ import { select } from "@inglorious/web/select"
972
+ import { table } from "@inglorious/web/table"
967
973
  ```
968
974
 
969
975
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inglorious/web",
3
- "version": "3.0.0",
3
+ "version": "3.0.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",
@@ -53,16 +53,19 @@
53
53
  },
54
54
  "files": [
55
55
  "src",
56
+ "!src/**/*.test.js",
56
57
  "types"
57
58
  ],
58
59
  "publishConfig": {
59
60
  "access": "public"
60
61
  },
61
- "sideEffects": false,
62
+ "sideEffects": [
63
+ "**/*.css"
64
+ ],
62
65
  "dependencies": {
63
66
  "lit-html": "^3.3.1",
64
- "@inglorious/utils": "3.7.0",
65
- "@inglorious/store": "8.0.0"
67
+ "@inglorious/store": "8.0.1",
68
+ "@inglorious/utils": "3.7.1"
66
69
  },
67
70
  "devDependencies": {
68
71
  "prettier": "^3.6.2",
@@ -1,372 +0,0 @@
1
- /**
2
- * @vitest-environment jsdom
3
- */
4
- import { beforeEach, describe, expect, it, vi } from "vitest"
5
-
6
- import { form, getFieldError, getFieldValue, isFieldTouched } from "."
7
-
8
- describe("form", () => {
9
- let entity
10
-
11
- beforeEach(() => {
12
- entity = {
13
- id: "test-form",
14
- initialValues: {
15
- name: "John Doe",
16
- contact: {
17
- email: "john.doe@example.com",
18
- },
19
- tags: ["a", "b"],
20
- },
21
- }
22
- })
23
-
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
-
41
- it("should reset the form to its initial state", () => {
42
- // mess up the state first
43
- entity.values = {}
44
- entity.isPristine = false
45
-
46
- form.create(entity)
47
-
48
- expect(entity.values).toEqual(entity.initialValues)
49
- expect(entity.values).not.toBe(entity.initialValues) // should be a clone
50
- expect(entity.isPristine).toBe(true)
51
- expect(entity.isValid).toBe(true)
52
- expect(entity.errors).toEqual({
53
- name: null,
54
- contact: { email: null },
55
- tags: [null, null],
56
- })
57
- expect(entity.touched).toEqual({
58
- name: null,
59
- contact: { email: null },
60
- tags: [null, null],
61
- })
62
- })
63
- })
64
-
65
- describe("reset()", () => {
66
- it("should reset the form to its initial state", () => {
67
- form.create(entity)
68
- entity.values.name = "Jane Doe"
69
- entity.isPristine = false
70
-
71
- form.reset(entity)
72
-
73
- expect(entity.values).toEqual(entity.initialValues)
74
- expect(entity.isPristine).toBe(true)
75
- })
76
- })
77
-
78
- describe("fieldChange()", () => {
79
- beforeEach(() => {
80
- form.create(entity)
81
- })
82
-
83
- it("should update a field's value, mark it as touched, and set form to dirty", () => {
84
- form.fieldChange(entity, { path: "name", value: "Jane Doe" })
85
-
86
- expect(entity.values.name).toBe("Jane Doe")
87
- expect(entity.touched.name).toBe(true)
88
- expect(entity.isPristine).toBe(false)
89
- })
90
-
91
- it("should update a nested field's value", () => {
92
- form.fieldChange(entity, {
93
- path: "contact.email",
94
- value: "jane.doe@example.com",
95
- })
96
-
97
- expect(entity.values.contact.email).toBe("jane.doe@example.com")
98
- expect(entity.touched.contact.email).toBe(true)
99
- })
100
-
101
- it("should validate the field if a validate function is provided", () => {
102
- const validate = (value) => (value.length < 10 ? "Too short" : null)
103
- form.fieldChange(entity, { path: "name", value: "Jane", validate })
104
-
105
- expect(entity.errors.name).toBe("Too short")
106
- expect(entity.isValid).toBe(false)
107
- })
108
-
109
- it("should clear a field's error when it becomes valid", () => {
110
- const validate = (value) => (value.length < 10 ? "Too short" : null)
111
- form.fieldChange(entity, { path: "name", value: "Jane", validate })
112
- expect(entity.isValid).toBe(false)
113
-
114
- form.fieldChange(entity, {
115
- path: "name",
116
- value: "Jane Doe The Great",
117
- validate,
118
- })
119
-
120
- expect(entity.errors.name).toBe(null)
121
- expect(entity.isValid).toBe(true)
122
- })
123
- })
124
-
125
- describe("fieldBlur()", () => {
126
- beforeEach(() => {
127
- form.create(entity)
128
- })
129
-
130
- it("should mark a field as touched", () => {
131
- form.fieldBlur(entity, { path: "name" })
132
- expect(entity.touched.name).toBe(true)
133
- })
134
-
135
- it("should validate the field if a validate function is provided", () => {
136
- const validate = (value) =>
137
- value === "John Doe" ? "Cannot be John" : null
138
- form.fieldBlur(entity, { path: "name", validate })
139
-
140
- expect(entity.errors.name).toBe("Cannot be John")
141
- expect(entity.isValid).toBe(false)
142
- })
143
- })
144
-
145
- describe("field array operations", () => {
146
- beforeEach(() => {
147
- form.create(entity)
148
- })
149
-
150
- it("fieldArrayAppend: should append a value and metadata", () => {
151
- form.fieldArrayAppend(entity, { path: "tags", value: "c" })
152
-
153
- expect(entity.values.tags).toEqual(["a", "b", "c"])
154
- expect(entity.errors.tags).toEqual([null, null, null])
155
- expect(entity.touched.tags).toEqual([null, null, null])
156
- expect(entity.isPristine).toBe(false)
157
- })
158
-
159
- it("fieldArrayRemove: should remove a value and metadata at an index", () => {
160
- form.fieldArrayRemove(entity, { path: "tags", index: 0 })
161
-
162
- expect(entity.values.tags).toEqual(["b"])
163
- expect(entity.errors.tags).toEqual([null])
164
- expect(entity.touched.tags).toEqual([null])
165
- expect(entity.isPristine).toBe(false)
166
- })
167
-
168
- it("fieldArrayInsert: should insert a value and metadata at an index", () => {
169
- form.fieldArrayInsert(entity, { path: "tags", index: 1, value: "c" })
170
-
171
- expect(entity.values.tags).toEqual(["a", "c", "b"])
172
- expect(entity.errors.tags).toEqual([null, null, null])
173
- expect(entity.touched.tags).toEqual([null, null, null])
174
- expect(entity.isPristine).toBe(false)
175
- })
176
-
177
- it("fieldArrayMove: should move a value and metadata", () => {
178
- entity.errors.tags[0] = "Error on A"
179
- entity.touched.tags[0] = true
180
-
181
- form.fieldArrayMove(entity, { path: "tags", fromIndex: 0, toIndex: 1 })
182
-
183
- expect(entity.values.tags).toEqual(["b", "a"])
184
- expect(entity.errors.tags).toEqual([null, "Error on A"])
185
- expect(entity.touched.tags).toEqual([null, true])
186
- expect(entity.isPristine).toBe(false)
187
- })
188
-
189
- it("fieldArray operations should warn if path is not an array", () => {
190
- const spy = vi.spyOn(console, "warn").mockImplementation(() => {})
191
-
192
- form.fieldArrayAppend(entity, { path: "name", value: "c" })
193
- expect(spy).toHaveBeenCalledWith("Field at path 'name' is not an array")
194
-
195
- spy.mockRestore()
196
- })
197
- })
198
-
199
- describe("validate()", () => {
200
- beforeEach(() => {
201
- form.create(entity)
202
- })
203
-
204
- it("should set errors and isValid based on the validation function", () => {
205
- const validate = (values) => {
206
- const errors = {}
207
- if (values.name !== "Jane Doe") {
208
- errors.name = "Name must be Jane Doe"
209
- }
210
- return errors
211
- }
212
-
213
- form.validate(entity, { validate })
214
-
215
- expect(entity.errors.name).toBe("Name must be Jane Doe")
216
- expect(entity.isValid).toBe(false)
217
- })
218
-
219
- it("should set isValid to true if there are no errors", () => {
220
- const validate = () => ({})
221
-
222
- entity.isValid = false
223
- form.validate(entity, { validate })
224
-
225
- expect(entity.errors).toEqual({})
226
- expect(entity.isValid).toBe(true)
227
- })
228
- })
229
-
230
- describe("validateAsync()", () => {
231
- let api
232
-
233
- beforeEach(() => {
234
- form.create(entity)
235
- api = { notify: vi.fn() }
236
- })
237
-
238
- it("should set isValidating to true and call the validation function", async () => {
239
- const validate = vi.fn().mockResolvedValue({})
240
- await form.validateAsync(entity, { validate }, api)
241
- expect(entity.isValidating).toBe(true)
242
- expect(validate).toHaveBeenCalledWith(entity.values)
243
- })
244
-
245
- it("should notify 'validationComplete' on successful validation", async () => {
246
- const errors = { name: "Required" }
247
- const validate = async () => errors
248
- await form.validateAsync(entity, { validate }, api)
249
-
250
- expect(api.notify).toHaveBeenCalledWith(
251
- `#${entity.id}:validationComplete`,
252
- {
253
- errors,
254
- isValid: false,
255
- },
256
- )
257
- })
258
-
259
- it("should notify 'validationError' on validation failure", async () => {
260
- const error = new Error("Validation failed")
261
- const validate = async () => {
262
- throw error
263
- }
264
- await form.validateAsync(entity, { validate }, api)
265
-
266
- expect(api.notify).toHaveBeenCalledWith(`#${entity.id}:validationError`, {
267
- error: "Validation failed",
268
- })
269
- })
270
- })
271
-
272
- describe("validationComplete()", () => {
273
- it("should update form state after async validation", () => {
274
- form.create(entity)
275
- entity.isValidating = true
276
- const errors = { name: "Error" }
277
-
278
- form.validationComplete(entity, { errors, isValid: false })
279
-
280
- expect(entity.isValidating).toBe(false)
281
- expect(entity.errors).toBe(errors)
282
- expect(entity.isValid).toBe(false)
283
- })
284
- })
285
-
286
- describe("validationError()", () => {
287
- it("should update form state after an async validation error", () => {
288
- form.create(entity)
289
- entity.isValidating = true
290
-
291
- form.validationError(entity, { error: "Network Error" })
292
-
293
- expect(entity.isValidating).toBe(false)
294
- expect(entity.submitError).toBe("Network Error")
295
- })
296
- })
297
- })
298
-
299
- describe("form helpers", () => {
300
- let testForm
301
-
302
- beforeEach(() => {
303
- testForm = {
304
- values: {
305
- user: { name: "Alex" },
306
- tags: ["one", "two"],
307
- },
308
- errors: {
309
- user: { name: "Too short" },
310
- tags: [null, "Invalid tag"],
311
- },
312
- touched: {
313
- user: { name: true },
314
- tags: [true, false],
315
- },
316
- }
317
- })
318
-
319
- describe("getFieldValue()", () => {
320
- it("should return the value of a field at a given path", () => {
321
- expect(getFieldValue(testForm, "user.name")).toBe("Alex")
322
- })
323
-
324
- it("should return an array value", () => {
325
- expect(getFieldValue(testForm, "tags")).toEqual(["one", "two"])
326
- })
327
-
328
- it("should return a default value if path does not exist", () => {
329
- expect(getFieldValue(testForm, "user.age", 30)).toBe(30)
330
- })
331
-
332
- it("should return undefined if path does not exist and no default is provided", () => {
333
- expect(getFieldValue(testForm, "user.age")).toBeUndefined()
334
- })
335
- })
336
-
337
- describe("getFieldError()", () => {
338
- it("should return the error of a field at a given path", () => {
339
- expect(getFieldError(testForm, "user.name")).toBe("Too short")
340
- })
341
-
342
- it("should return the error of a field in an array", () => {
343
- expect(getFieldError(testForm, "tags.1")).toBe("Invalid tag")
344
- })
345
-
346
- it("should return null for a valid field in an array", () => {
347
- expect(getFieldError(testForm, "tags.0")).toBeNull()
348
- })
349
-
350
- it("should return undefined if path does not exist", () => {
351
- expect(getFieldError(testForm, "user.age")).toBe(undefined)
352
- })
353
- })
354
-
355
- describe("isFieldTouched()", () => {
356
- it("should return true if a field has been touched", () => {
357
- expect(isFieldTouched(testForm, "user.name")).toBe(true)
358
- })
359
-
360
- it("should return true for a touched field in an array", () => {
361
- expect(isFieldTouched(testForm, "tags.0")).toBe(true)
362
- })
363
-
364
- it("should return false for an untouched field in an array", () => {
365
- expect(isFieldTouched(testForm, "tags.1")).toBe(false)
366
- })
367
-
368
- it("should return false if path does not exist", () => {
369
- expect(isFieldTouched(testForm, "user.age")).toBe(false)
370
- })
371
- })
372
- })