@inglorious/web 4.3.0 → 4.5.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/src/form/index.js CHANGED
@@ -1,375 +1,6 @@
1
- import { clone, get, set } from "@inglorious/utils/data-structures/object.js"
1
+ import * as handlers from "./handlers.js"
2
+ import * as helpers from "./helpers.js"
2
3
 
3
- /**
4
- * @typedef {import('../../types/form.js').FormEntity} FormEntity
5
- * @typedef {import('../../types/form.js').FormFieldChangePayload} FormFieldChangePayload
6
- * @typedef {import('../../types/form.js').FormFieldBlurPayload} FormFieldBlurPayload
7
- *
8
- * @typedef {object} FormValidatePayload
9
- * @property {string|number} entityId - The ID of the target form entity.
10
- * @property {(values: object) => import('../../types/form.js').FormErrors<object>} validate - A function that validates the entire form's values and returns a complete error object.
11
- *
12
- * @typedef {object} FormValidateAsyncPayload
13
- * @property {string|number} entityId - The ID of the target form entity.
14
- * @property {(values: object) => Promise<import('../../types/form.js').FormErrors<object>>} validate - An async function that validates the entire form's values.
15
- *
16
- * @typedef {object} FormValidationCompletePayload
17
- * @property {string|number} entityId - The ID of the target form entity.
18
- * @property {import('../../types/form.js').FormErrors<object>} errors - The validation errors.
19
- * @property {boolean} isValid - Whether the form is valid.
20
- *
21
- * @typedef {object} FormValidationErrorPayload
22
- * @property {string|number} entityId - The ID of the target form entity.
23
- * @property {string} error - The error message.
24
- */
4
+ export const form = { ...handlers }
25
5
 
26
- const NO_ITEMS_REMOVED = 0
27
- const ONE_ITEM_REMOVED = 1
28
-
29
- let areListenersInitialized = false
30
-
31
- /**
32
- * A type definition for managing form state within an entity-based system.
33
- * It handles initialization, field changes, validation, and submission.
34
- *
35
- * @type {import('@inglorious/engine/types/type.js').Type}
36
- */
37
- export const form = {
38
- /**
39
- * Initializes the form entity by resetting it to its initial state.
40
- * @param {FormEntity} entity - The form entity.
41
- */
42
- init() {
43
- if (areListenersInitialized) return
44
-
45
- document.addEventListener("click", (event) => {
46
- const button = event.target.closest("button")
47
-
48
- if (!button || button.getAttribute("type") === "submit") return
49
-
50
- event.preventDefault()
51
- })
52
-
53
- document.addEventListener("submit", (event) => {
54
- const form = event.target.closest("form")
55
-
56
- if (!form) return
57
- event.preventDefault()
58
- })
59
-
60
- areListenersInitialized = true
61
- },
62
-
63
- /**
64
- * Resets the form entity with default state.
65
- * @param {FormEntity} entity - The form entity.
66
- */
67
- create(entity) {
68
- resetForm(entity)
69
- },
70
-
71
- /**
72
- * Appends an item to a field array.
73
- * @param {FormEntity} entity - The form entity.
74
- * @param {Object} payload
75
- * @param {string|number} payload.entityId - The form entity ID.
76
- * @param {string} payload.path - Path to the array field (e.g., 'addresses').
77
- * @param {*} payload.value - The value to append.
78
- */
79
- fieldArrayAppend(entity, { path, value }) {
80
- const array = get(entity.values, path)
81
- if (!Array.isArray(array)) {
82
- console.warn(`Field at path '${path}' is not an array`)
83
- return
84
- }
85
-
86
- array.push(value)
87
-
88
- const errorsArray = get(entity.errors, path, [])
89
- errorsArray.push(initMetadata(value))
90
-
91
- const touchedArray = get(entity.touched, path, [])
92
- touchedArray.push(initMetadata(value))
93
-
94
- entity.isPristine = false
95
- },
96
-
97
- /**
98
- * Removes an item from a field array by index.
99
- * @param {FormEntity} entity - The form entity.
100
- * @param {Object} payload
101
- * @param {string|number} payload.entityId - The form entity ID.
102
- * @param {string} payload.path - Path to the array field.
103
- * @param {number} payload.index - The index to remove.
104
- */
105
- fieldArrayRemove(entity, { path, index }) {
106
- const array = get(entity.values, path)
107
- if (!Array.isArray(array)) {
108
- console.warn(`Field at path '${path}' is not an array`)
109
- return
110
- }
111
-
112
- array.splice(index, ONE_ITEM_REMOVED)
113
-
114
- const errorsArray = get(entity.errors, path)
115
- errorsArray.splice(index, ONE_ITEM_REMOVED)
116
-
117
- const touchedArray = get(entity.touched, path)
118
- touchedArray.splice(index, ONE_ITEM_REMOVED)
119
-
120
- entity.isPristine = false
121
- },
122
-
123
- /**
124
- * Inserts an item into a field array at a specific index.
125
- * @param {FormEntity} entity - The form entity.
126
- * @param {Object} payload
127
- * @param {string|number} payload.entityId - The form entity ID.
128
- * @param {string} payload.path - Path to the array field.
129
- * @param {number} payload.index - The index to insert at.
130
- * @param {*} payload.value - The value to insert.
131
- */
132
- fieldArrayInsert(entity, { path, index, value }) {
133
- const array = get(entity.values, path)
134
- if (!Array.isArray(array)) {
135
- console.warn(`Field at path '${path}' is not an array`)
136
- return
137
- }
138
-
139
- array.splice(index, NO_ITEMS_REMOVED, value)
140
-
141
- const errorsArray = get(entity.errors, path)
142
- errorsArray.splice(index, NO_ITEMS_REMOVED, initMetadata(value))
143
-
144
- const touchedArray = get(entity.touched, path)
145
- touchedArray.splice(index, NO_ITEMS_REMOVED, initMetadata(value))
146
-
147
- entity.isPristine = false
148
- },
149
-
150
- /**
151
- * Moves an item in a field array from one index to another.
152
- * @param {FormEntity} entity - The form entity.
153
- * @param {Object} payload
154
- * @param {string|number} payload.entityId - The form entity ID.
155
- * @param {string} payload.path - Path to the array field.
156
- * @param {number} payload.fromIndex - The source index.
157
- * @param {number} payload.toIndex - The destination index.
158
- */
159
- fieldArrayMove(entity, { path, fromIndex, toIndex }) {
160
- const array = get(entity.values, path)
161
- if (!Array.isArray(array)) {
162
- console.warn(`Field at path '${path}' is not an array`)
163
- return
164
- }
165
-
166
- const [item] = array.splice(fromIndex, ONE_ITEM_REMOVED)
167
- array.splice(toIndex, NO_ITEMS_REMOVED, item)
168
-
169
- const errorsArray = get(entity.errors, path)
170
- const [errorItem] = errorsArray.splice(fromIndex, ONE_ITEM_REMOVED)
171
- errorsArray.splice(toIndex, NO_ITEMS_REMOVED, errorItem)
172
-
173
- const touchedArray = get(entity.touched, path)
174
- const [touchedItem] = touchedArray.splice(fromIndex, ONE_ITEM_REMOVED)
175
- touchedArray.splice(toIndex, NO_ITEMS_REMOVED, touchedItem)
176
-
177
- entity.isPristine = false
178
- },
179
-
180
- /**
181
- * Handles the blur event for a form field, marking it as touched and optionally validating it.
182
- * @param {FormEntity} entity - The form entity.
183
- * @param {FormFieldBlurPayload} payload - The event payload.
184
- */
185
- fieldBlur(entity, { path, validate }) {
186
- set(entity.touched, path, true)
187
-
188
- if (!validate) return
189
-
190
- const error = validate(get(entity.values, path))
191
- setFieldError(entity, path, error)
192
- },
193
-
194
- /**
195
- * Handles a change in a form field's value and optionally validates it.
196
- * @param {FormEntity} entity - The form entity.
197
- * @param {FormFieldChangePayload} payload - The event payload.
198
- */
199
- fieldChange(entity, { path, value, validate }) {
200
- setFieldValue(entity, path, value)
201
-
202
- if (!validate) return
203
-
204
- const error = validate(get(entity.values, path))
205
- setFieldError(entity, path, error)
206
- },
207
-
208
- /**
209
- * Resets the form to its initial state.
210
- * @param {FormEntity} entity - The form entity.
211
- * @param {string|number} entityId - The ID of the target form entity.
212
- */
213
- reset(entity) {
214
- resetForm(entity)
215
- },
216
-
217
- /**
218
- * Synchronously validates the entire form.
219
- * @param {FormEntity} entity - The form entity.
220
- * @param {FormValidatePayload} payload - The event payload.
221
- */
222
- validate(entity, { validate }) {
223
- entity.errors = validate(entity.values)
224
- entity.isValid = !hasErrors(entity.errors)
225
- },
226
-
227
- /**
228
- * Asynchronously validates the entire form.
229
- * Dispatches 'formValidationComplete' on success or 'formValidationError' on failure.
230
- * @param {FormEntity} entity - The form entity.
231
- * @param {FormValidateAsyncPayload} payload - The event payload.
232
- * @param {Object} api - The API object for notifying events.
233
- */
234
- async validateAsync(entity, { validate }, api) {
235
- try {
236
- entity.isValidating = true
237
- const errors = await validate(entity.values)
238
- api.notify(`#${entity.id}:validationComplete`, {
239
- errors,
240
- isValid: !hasErrors(errors),
241
- })
242
- } catch (error) {
243
- api.notify(`#${entity.id}:validationError`, {
244
- error: error.message,
245
- })
246
- }
247
- },
248
-
249
- /**
250
- * Handles the completion of async validation.
251
- * @param {FormEntity} entity - The form entity.
252
- * @param {FormValidationCompletePayload} payload - The event payload.
253
- */
254
- validationComplete(entity, { errors, isValid }) {
255
- entity.isValidating = false
256
- entity.errors = errors
257
- entity.isValid = isValid
258
- },
259
-
260
- /**
261
- * Handles validation errors from async validation.
262
- * @param {FormEntity} entity - The form entity.
263
- * @param {FormValidationErrorPayload} payload - The event payload.
264
- */
265
- validationError(entity, { error }) {
266
- entity.isValidating = false
267
- entity.submitError = error
268
- },
269
- }
270
-
271
- // Helper functions
272
-
273
- /**
274
- * Retrieves the validation error for a specific form field.
275
- * @param {FormEntity} form - The form entity object.
276
- * @param {string} path - The dot-separated path to the field (e.g., 'user.name').
277
- * @returns {string|null|undefined} The error message for the field, or null/undefined if there is no error.
278
- */
279
- export function getFieldError(form, path) {
280
- return get(form.errors, path)
281
- }
282
-
283
- /**
284
- * Retrieves the value of a specific form field.
285
- * @param {FormEntity} form - The form entity object.
286
- * @param {string} path - The dot-separated path to the field (e.g., 'user.name').
287
- * @param {*} [defaultValue] - An optional default value to return if the path does not exist.
288
- * @returns {*} The value of the field, or the default value if not found.
289
- */
290
- export function getFieldValue(form, path, defaultValue) {
291
- return get(form.values, path, defaultValue)
292
- }
293
-
294
- /**
295
- * Checks if a specific form field has been touched (i.e., has received a blur event).
296
- * @param {FormEntity} form - The form entity object.
297
- * @param {string} path - The dot-separated path to the field (e.g., 'user.name').
298
- * @returns {boolean} `true` if the field has been touched, otherwise `false`.
299
- */
300
- export function isFieldTouched(form, path) {
301
- return Boolean(get(form.touched, path))
302
- }
303
-
304
- // Private helper functions
305
-
306
- function hasErrors(errors) {
307
- if (errors === null || errors === undefined) {
308
- return false
309
- }
310
-
311
- if (typeof errors !== "object") {
312
- // Leaf value - check if it's truthy (error string)
313
- return Boolean(errors)
314
- }
315
-
316
- if (Array.isArray(errors)) {
317
- return errors.some((item) => hasErrors(item))
318
- }
319
-
320
- // Object - check all properties
321
- return Object.values(errors).some((value) => hasErrors(value))
322
- }
323
-
324
- function initMetadata(value) {
325
- if (Array.isArray(value)) {
326
- return value.map((item) => initMetadata(item))
327
- }
328
-
329
- if (typeof value === "object" && value !== null) {
330
- return Object.fromEntries(
331
- Object.entries(value).map(([key, val]) => [key, initMetadata(val)]),
332
- )
333
- }
334
-
335
- return null
336
- }
337
-
338
- function resetForm(form) {
339
- form.values = clone(form.initialValues)
340
- form.errors = initMetadata(form.initialValues)
341
- form.touched = initMetadata(form.initialValues)
342
-
343
- form.isValid = true
344
- form.isPristine = true
345
-
346
- form.isValidating = false
347
- form.isSubmitting = false
348
- }
349
-
350
- function setFieldValue(form, path, value) {
351
- set(form.values, path, value)
352
- set(form.touched, path, true)
353
-
354
- form.isPristine = false
355
-
356
- // By clearing the error for this path, the form might become valid.
357
- // We delegate to setFieldError(..., null) to correctly run the check.
358
- setFieldError(form, path, null)
359
- }
360
-
361
- function setFieldError(form, path, error) {
362
- // Ensure we set null if error is falsy, but not an empty string
363
- const newError = error || null
364
- set(form.errors, path, newError)
365
-
366
- if (newError) {
367
- // If we are adding an error, the form is definitely invalid.
368
- // No need for an expensive check.
369
- form.isValid = false
370
- } else {
371
- // If we are removing an error, the form MIGHT become valid.
372
- // This is when we must perform the check.
373
- form.isValid = !hasErrors(form.errors)
374
- }
375
- }
6
+ export const { getFieldError, getFieldValue, isFieldTouched } = helpers
package/src/hydrate.js ADDED
@@ -0,0 +1 @@
1
+ import "@lit-labs/ssr-client/lit-element-hydrate-support.js"
package/src/index.js CHANGED
@@ -12,3 +12,4 @@ export { repeat } from "lit-html/directives/repeat.js"
12
12
  export { styleMap } from "lit-html/directives/style-map.js"
13
13
  export { unsafeHTML } from "lit-html/directives/unsafe-html.js"
14
14
  export { when } from "lit-html/directives/when.js"
15
+ export { html as staticHtml, unsafeStatic } from "lit-html/static.js"