@inglorious/web 2.0.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/LICENSE +9 -0
- package/README.md +383 -0
- package/package.json +54 -0
- package/src/form.js +371 -0
- package/src/index.js +10 -0
- package/src/list.js +105 -0
- package/src/mount.js +49 -0
- package/src/router.js +268 -0
- package/types/form.d.ts +377 -0
- package/types/index.d.ts +2 -0
- package/types/mount.d.ts +24 -0
- package/types/router.d.ts +111 -0
package/src/router.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
const SKIP_FULL_MATCH_GROUP = 1 // .match() result at index 0 is the full string
|
|
2
|
+
const REMOVE_COLON_PREFIX = 1
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {Object} RouterEntity
|
|
6
|
+
* @property {string} id - The unique identifier for the router entity.
|
|
7
|
+
* @property {Object.<string, string>} routes - A map of URL patterns to entity types.
|
|
8
|
+
* @property {string} [path] - The current URL path.
|
|
9
|
+
* @property {string} [route] - The entity type for the current route.
|
|
10
|
+
* @property {Object.<string, string>} [params] - The parameters extracted from the URL.
|
|
11
|
+
* @property {Object.<string, string>} [query] - The query parameters from the URL.
|
|
12
|
+
* @property {string} [hash] - The URL hash.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A client-side router type for an entity-based system. It handles URL changes,
|
|
17
|
+
* link interception, and history management (pushState, replaceState, popstate).
|
|
18
|
+
* @type {import('@inglorious/engine/types/type.js').Type}
|
|
19
|
+
*/
|
|
20
|
+
export const router = {
|
|
21
|
+
/**
|
|
22
|
+
* Initializes the router. It handles the initial route, sets up a `popstate`
|
|
23
|
+
* listener for browser navigation, and intercepts clicks on local `<a>` links.
|
|
24
|
+
* @param {RouterEntity} entity - The router entity.
|
|
25
|
+
* @param {*} payload - The message payload (unused).
|
|
26
|
+
* @param {import('../types/router.js').RouterApi} api - The router API.
|
|
27
|
+
*/
|
|
28
|
+
init(entity, payload, api) {
|
|
29
|
+
// Handle initial route
|
|
30
|
+
const initialPath = window.location.pathname
|
|
31
|
+
const route = findRoute(entity.routes, initialPath)
|
|
32
|
+
|
|
33
|
+
if (route) {
|
|
34
|
+
entity.path = route.path
|
|
35
|
+
entity.route = route.entityType
|
|
36
|
+
entity.params = route.params
|
|
37
|
+
|
|
38
|
+
const query = Object.fromEntries(
|
|
39
|
+
new URLSearchParams(window.location.search),
|
|
40
|
+
)
|
|
41
|
+
entity.query = query
|
|
42
|
+
entity.hash = window.location.hash
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const id = entity.id
|
|
46
|
+
// Listen for browser back/forward
|
|
47
|
+
window.addEventListener("popstate", () => {
|
|
48
|
+
const path = window.location.pathname
|
|
49
|
+
const { routes } = api.getEntity(id)
|
|
50
|
+
const route = findRoute(routes, path)
|
|
51
|
+
|
|
52
|
+
if (route) {
|
|
53
|
+
api.notify("routeSync", {
|
|
54
|
+
path: route.path,
|
|
55
|
+
entityType: route.entityType,
|
|
56
|
+
params: route.params,
|
|
57
|
+
query: Object.fromEntries(
|
|
58
|
+
new URLSearchParams(window.location.search),
|
|
59
|
+
),
|
|
60
|
+
hash: window.location.hash,
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// Intercept link clicks
|
|
66
|
+
document.addEventListener("click", (e) => {
|
|
67
|
+
// Find the closest <a> tag (handles clicks on children)
|
|
68
|
+
const link = e.target.closest("a")
|
|
69
|
+
|
|
70
|
+
if (!link) return
|
|
71
|
+
|
|
72
|
+
// Skip external links
|
|
73
|
+
if (link.target === "_blank") return
|
|
74
|
+
if (link.rel === "external") return
|
|
75
|
+
if (link.hasAttribute("data-external")) return
|
|
76
|
+
|
|
77
|
+
// Skip different origin links
|
|
78
|
+
if (link.origin !== window.location.origin) return
|
|
79
|
+
|
|
80
|
+
// Skip links with download attribute
|
|
81
|
+
if (link.hasAttribute("download")) return
|
|
82
|
+
|
|
83
|
+
// Skip mailto:, tel:, etc.
|
|
84
|
+
if (!["http:", "https:"].includes(link.protocol)) return
|
|
85
|
+
|
|
86
|
+
// Prevent default and use router
|
|
87
|
+
e.preventDefault()
|
|
88
|
+
|
|
89
|
+
const path = link.pathname + link.search + link.hash
|
|
90
|
+
api.notify("navigate", path)
|
|
91
|
+
})
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Navigates to a new route, updating the browser's history and the router entity state.
|
|
96
|
+
* @param {RouterEntity} entity - The router entity.
|
|
97
|
+
* @param {string|number|import('../types/router.js').NavigatePayload} payload - The navigation payload.
|
|
98
|
+
* Can be a path string, a number for `history.go()`, or an object with navigation options.
|
|
99
|
+
* @param {string|number} payload.to - The destination path or history offset.
|
|
100
|
+
* @param {Object} [payload.params] - Route parameters to build the path from a pattern.
|
|
101
|
+
* @param {boolean} [payload.replace] - If true, uses `history.replaceState` instead of `pushState`.
|
|
102
|
+
* @param {Object} [payload.state] - Additional state to store in the browser's history.
|
|
103
|
+
* @param {import('../types/router.js').RouterApi} api - The router API.
|
|
104
|
+
*/
|
|
105
|
+
navigate(entity, payload, api) {
|
|
106
|
+
if (["number", "string"].includes(typeof payload)) {
|
|
107
|
+
payload = { to: payload }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const { to, params, replace, state = {} } = payload
|
|
111
|
+
|
|
112
|
+
// Numeric navigation (back/forward)
|
|
113
|
+
if (typeof to === "number") {
|
|
114
|
+
history.go(to)
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Build final path
|
|
119
|
+
let path = to
|
|
120
|
+
|
|
121
|
+
// If params provided and "to" looks like a pattern, build the path
|
|
122
|
+
if (params && to.includes(":")) {
|
|
123
|
+
path = buildPath(to, params)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// If "to" is already a final path (like "/users/1"), use it directly
|
|
127
|
+
// The router will match it against patterns in entity.routes
|
|
128
|
+
|
|
129
|
+
const route = findRoute(entity.routes, path)
|
|
130
|
+
|
|
131
|
+
if (!route) {
|
|
132
|
+
console.warn(`No route matches path: ${path}`)
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Prevent navigation if the full path (including query/hash) is identical.
|
|
137
|
+
const currentFullPath =
|
|
138
|
+
entity.path + window.location.search + window.location.hash
|
|
139
|
+
if (path === currentFullPath) {
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Parse query string
|
|
144
|
+
const [pathname, search] = path.split("?")
|
|
145
|
+
const query = search ? Object.fromEntries(new URLSearchParams(search)) : {}
|
|
146
|
+
|
|
147
|
+
// Update entity with routing info
|
|
148
|
+
entity.path = pathname
|
|
149
|
+
entity.route = route.entityType
|
|
150
|
+
entity.params = route.params
|
|
151
|
+
entity.query = query
|
|
152
|
+
entity.hash = window.location.hash
|
|
153
|
+
|
|
154
|
+
// Prepare history state
|
|
155
|
+
const historyState = {
|
|
156
|
+
...state,
|
|
157
|
+
route: route.entityType,
|
|
158
|
+
params: route.params,
|
|
159
|
+
query,
|
|
160
|
+
path: pathname,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Navigate
|
|
164
|
+
const method = replace ? "replaceState" : "pushState"
|
|
165
|
+
history[method](historyState, "", path)
|
|
166
|
+
|
|
167
|
+
api.notify("routeChange", historyState)
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Synchronizes the router entity's state with data from a routing event,
|
|
172
|
+
* typically triggered by a `popstate` event (browser back/forward).
|
|
173
|
+
* @param {RouterEntity} entity - The router entity to update.
|
|
174
|
+
* @param {import('../types/router.js').RouteSyncPayload} payload - The new route state.
|
|
175
|
+
*/
|
|
176
|
+
routeSync(entity, payload) {
|
|
177
|
+
entity.path = payload.path
|
|
178
|
+
entity.route = payload.entityType
|
|
179
|
+
entity.params = payload.params
|
|
180
|
+
entity.query = payload.query
|
|
181
|
+
entity.hash = payload.hash
|
|
182
|
+
},
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Builds a URL path by substituting parameters into a route pattern.
|
|
187
|
+
* Example: `buildPath("/users/:userId", { userId: "123" })` returns `"/users/123"`.
|
|
188
|
+
* @param {string} pattern - The route pattern (e.g., "/users/:userId").
|
|
189
|
+
* @param {Object.<string, string>} [params={}] - The parameters to substitute.
|
|
190
|
+
* @returns {string} The constructed path.
|
|
191
|
+
*/
|
|
192
|
+
function buildPath(pattern, params = {}) {
|
|
193
|
+
let path = pattern
|
|
194
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
195
|
+
path = path.replace(`:${key}`, value)
|
|
196
|
+
})
|
|
197
|
+
return path
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Finds a matching route configuration for a given URL path.
|
|
202
|
+
* It supports parameterized routes and a fallback "*" route.
|
|
203
|
+
* @param {Object.<string, string>} routes - The routes configuration map.
|
|
204
|
+
* @param {string} path - The URL path to match.
|
|
205
|
+
* @returns {{pattern: string, entityType: string, params: Object, path: string}|null}
|
|
206
|
+
* The matched route object or null if no match is found.
|
|
207
|
+
*/
|
|
208
|
+
function findRoute(routes, path) {
|
|
209
|
+
const [pathname] = path.split("?")
|
|
210
|
+
let fallbackRoute = null
|
|
211
|
+
|
|
212
|
+
for (const [pattern, entityType] of Object.entries(routes)) {
|
|
213
|
+
if (pattern === "*") {
|
|
214
|
+
fallbackRoute = { pattern, entityType, params: {}, path: pathname }
|
|
215
|
+
continue
|
|
216
|
+
}
|
|
217
|
+
const params = matchRoute(pattern, pathname)
|
|
218
|
+
if (params !== null) {
|
|
219
|
+
return { pattern, entityType, params, path: pathname }
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return fallbackRoute
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Matches a URL path against a route pattern and extracts any parameters.
|
|
227
|
+
* @param {string} pattern - The route pattern (e.g., "/users/:userId").
|
|
228
|
+
* @param {string} path - The URL path to match (e.g., "/users/123").
|
|
229
|
+
* @returns {Object.<string, string>|null} An object of extracted parameters,
|
|
230
|
+
* or null if the path does not match the pattern.
|
|
231
|
+
*/
|
|
232
|
+
function matchRoute(pattern, path) {
|
|
233
|
+
const paramNames = getParamNames(pattern)
|
|
234
|
+
const regex = patternToRegex(pattern)
|
|
235
|
+
const match = path.match(regex)
|
|
236
|
+
|
|
237
|
+
if (!match) return null
|
|
238
|
+
|
|
239
|
+
const params = {}
|
|
240
|
+
paramNames.forEach((name, i) => {
|
|
241
|
+
params[name] = match[i + SKIP_FULL_MATCH_GROUP]
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
return params
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Parses a route pattern and extracts the names of its parameters.
|
|
249
|
+
* Example: `getParamNames("/users/:userId/posts/:postId")` returns `["userId", "postId"]`.
|
|
250
|
+
* @param {string} pattern - The route pattern.
|
|
251
|
+
* @returns {string[]} An array of parameter names.
|
|
252
|
+
*/
|
|
253
|
+
function getParamNames(pattern) {
|
|
254
|
+
const matches = pattern.match(/:(\w+)/g)
|
|
255
|
+
return matches ? matches.map((m) => m.slice(REMOVE_COLON_PREFIX)) : []
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Converts a route pattern into a regular expression for matching URL paths.
|
|
260
|
+
* @param {string} pattern - The route pattern (e.g., "/users/:userId").
|
|
261
|
+
* @returns {RegExp} The corresponding regular expression.
|
|
262
|
+
*/
|
|
263
|
+
function patternToRegex(pattern) {
|
|
264
|
+
const regexPattern = pattern
|
|
265
|
+
.replace(/\//g, "\\/")
|
|
266
|
+
.replace(/:(\w+)/g, "([^\\/]+)")
|
|
267
|
+
return new RegExp(`^${regexPattern}$`)
|
|
268
|
+
}
|
package/types/form.d.ts
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents the structure of form values, which can be a nested object.
|
|
3
|
+
*/
|
|
4
|
+
export type FormValues = Record<string, any>
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A recursive type that mirrors the structure of `T` (the form values),
|
|
8
|
+
* but with leaf nodes as `string | null`. Used for validation errors.
|
|
9
|
+
*/
|
|
10
|
+
export type FormErrors<T> = T extends (infer U)[]
|
|
11
|
+
? FormErrors<U>[]
|
|
12
|
+
: T extends object
|
|
13
|
+
? { [P in keyof T]?: FormErrors<T[P]> }
|
|
14
|
+
: string | null
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A recursive type that mirrors the structure of `T` (the form values),
|
|
18
|
+
* but with leaf nodes as `boolean`. Used for tracking touched fields.
|
|
19
|
+
*/
|
|
20
|
+
export type FormTouched<T> = T extends (infer U)[]
|
|
21
|
+
? FormTouched<U>[]
|
|
22
|
+
: T extends object
|
|
23
|
+
? { [P in keyof T]?: FormTouched<T[P]> }
|
|
24
|
+
: boolean
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Represents the state of a form entity. It is generic over the shape
|
|
28
|
+
* of the form's values.
|
|
29
|
+
*/
|
|
30
|
+
export interface FormEntity<T extends FormValues = FormValues> {
|
|
31
|
+
/** A unique identifier for the form entity. */
|
|
32
|
+
id: string | number
|
|
33
|
+
/** The initial values of the form, used for resetting. */
|
|
34
|
+
initialValues: T
|
|
35
|
+
/** The current values of the form fields. */
|
|
36
|
+
values: T
|
|
37
|
+
/** The current validation errors for the form fields. */
|
|
38
|
+
errors: FormErrors<T>
|
|
39
|
+
/** A map indicating if a field has been touched. */
|
|
40
|
+
touched: FormTouched<T>
|
|
41
|
+
/** A flag indicating if the form has any validation errors. */
|
|
42
|
+
isValid: boolean
|
|
43
|
+
/** A flag indicating if the form has not been touched. */
|
|
44
|
+
isPristine: boolean
|
|
45
|
+
/** A flag indicating if async validation is in progress. */
|
|
46
|
+
isValidating: boolean
|
|
47
|
+
/** A flag indicating if the form is currently being submitted. */
|
|
48
|
+
isSubmitting: boolean
|
|
49
|
+
/** An error message from form submission failure. */
|
|
50
|
+
submitError?: string | null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The payload for the `formFieldChange` event.
|
|
55
|
+
*/
|
|
56
|
+
export interface FormFieldChangePayload<T extends FormValues = FormValues> {
|
|
57
|
+
/** The ID of the target form entity. */
|
|
58
|
+
entityId: string | number
|
|
59
|
+
/** The dot-notation path to the field (e.g., 'user.name'). */
|
|
60
|
+
path: string
|
|
61
|
+
/** The new value of the field. */
|
|
62
|
+
value: any
|
|
63
|
+
/** An optional function to validate the field's value. */
|
|
64
|
+
validate?: (value: any) => string | null
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The payload for the `formFieldBlur` event.
|
|
69
|
+
*/
|
|
70
|
+
export interface FormFieldBlurPayload {
|
|
71
|
+
/** The ID of the target form entity. */
|
|
72
|
+
entityId: string | number
|
|
73
|
+
/** The dot-notation path to the field (e.g., 'user.name'). */
|
|
74
|
+
path: string
|
|
75
|
+
/** An optional function to validate the field's value on blur. */
|
|
76
|
+
validate?: (value: any) => string | null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The payload for the `fieldArrayAppend` event.
|
|
81
|
+
*/
|
|
82
|
+
export interface FieldArrayAppendPayload {
|
|
83
|
+
/** The ID of the target form entity. */
|
|
84
|
+
entityId: string | number
|
|
85
|
+
/** The dot-notation path to the array field. */
|
|
86
|
+
path: string
|
|
87
|
+
/** The value to append to the array. */
|
|
88
|
+
value: any
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The payload for the `fieldArrayRemove` event.
|
|
93
|
+
*/
|
|
94
|
+
export interface FieldArrayRemovePayload {
|
|
95
|
+
/** The ID of the target form entity. */
|
|
96
|
+
entityId: string | number
|
|
97
|
+
/** The dot-notation path to the array field. */
|
|
98
|
+
path: string
|
|
99
|
+
/** The index of the item to remove. */
|
|
100
|
+
index: number
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The payload for the `fieldArrayInsert` event.
|
|
105
|
+
*/
|
|
106
|
+
export interface FieldArrayInsertPayload {
|
|
107
|
+
/** The ID of the target form entity. */
|
|
108
|
+
entityId: string | number
|
|
109
|
+
/** The dot-notation path to the array field. */
|
|
110
|
+
path: string
|
|
111
|
+
/** The index at which to insert the new item. */
|
|
112
|
+
index: number
|
|
113
|
+
/** The value to insert into the array. */
|
|
114
|
+
value: any
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The payload for the `fieldArrayMove` event.
|
|
119
|
+
*/
|
|
120
|
+
export interface FieldArrayMovePayload {
|
|
121
|
+
/** The ID of the target form entity. */
|
|
122
|
+
entityId: string | number
|
|
123
|
+
/** The dot-notation path to the array field. */
|
|
124
|
+
path: string
|
|
125
|
+
/** The source index of the item to move. */
|
|
126
|
+
fromIndex: number
|
|
127
|
+
/** The destination index for the item. */
|
|
128
|
+
toIndex: number
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The payload for the `formReset` event.
|
|
133
|
+
*/
|
|
134
|
+
export interface FormResetPayload {
|
|
135
|
+
/** The ID of the target form entity. */
|
|
136
|
+
entityId: string | number
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The payload for the `formValidate` event (synchronous validation).
|
|
141
|
+
*/
|
|
142
|
+
export interface FormValidatePayload<T extends FormValues = FormValues> {
|
|
143
|
+
/** The ID of the target form entity. */
|
|
144
|
+
entityId: string | number
|
|
145
|
+
/**
|
|
146
|
+
* A function that validates the entire form's values and returns
|
|
147
|
+
* a complete error object.
|
|
148
|
+
*/
|
|
149
|
+
validate: (values: T) => FormErrors<T>
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The payload for the `formValidateAsync` event (asynchronous validation).
|
|
154
|
+
*/
|
|
155
|
+
export interface FormValidateAsyncPayload<T extends FormValues = FormValues> {
|
|
156
|
+
/** The ID of the target form entity. */
|
|
157
|
+
entityId: string | number
|
|
158
|
+
/**
|
|
159
|
+
* An async function that validates the entire form's values and returns
|
|
160
|
+
* a complete error object.
|
|
161
|
+
*/
|
|
162
|
+
validate: (values: T) => Promise<FormErrors<T>>
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The payload for the `formValidationComplete` event.
|
|
167
|
+
*/
|
|
168
|
+
export interface FormValidationCompletePayload<
|
|
169
|
+
T extends FormValues = FormValues,
|
|
170
|
+
> {
|
|
171
|
+
/** The ID of the target form entity. */
|
|
172
|
+
entityId: string | number
|
|
173
|
+
/** The validation errors returned from async validation. */
|
|
174
|
+
errors: FormErrors<T>
|
|
175
|
+
/** Whether the form is valid (no errors). */
|
|
176
|
+
isValid: boolean
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* The payload for the `formValidationError` event.
|
|
181
|
+
*/
|
|
182
|
+
export interface FormValidationErrorPayload {
|
|
183
|
+
/** The ID of the target form entity. */
|
|
184
|
+
entityId: string | number
|
|
185
|
+
/** The error message from the failed validation. */
|
|
186
|
+
error: string
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* The form type implementation for the entity-based state manager.
|
|
191
|
+
*/
|
|
192
|
+
export declare const form: {
|
|
193
|
+
/**
|
|
194
|
+
* Initializes the form entity by resetting it to its initial state.
|
|
195
|
+
* @param entity The form entity.
|
|
196
|
+
*/
|
|
197
|
+
init<T extends FormValues>(entity: FormEntity<T>): void
|
|
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
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Appends an item to a field array.
|
|
211
|
+
* @param entity The form entity.
|
|
212
|
+
* @param payload The append event payload.
|
|
213
|
+
*/
|
|
214
|
+
fieldArrayAppend<T extends FormValues>(
|
|
215
|
+
entity: FormEntity<T>,
|
|
216
|
+
payload: FieldArrayAppendPayload,
|
|
217
|
+
): void
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Removes an item from a field array by index.
|
|
221
|
+
* @param entity The form entity.
|
|
222
|
+
* @param payload The remove event payload.
|
|
223
|
+
*/
|
|
224
|
+
fieldArrayRemove<T extends FormValues>(
|
|
225
|
+
entity: FormEntity<T>,
|
|
226
|
+
payload: FieldArrayRemovePayload,
|
|
227
|
+
): void
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Inserts an item into a field array at a specific index.
|
|
231
|
+
* @param entity The form entity.
|
|
232
|
+
* @param payload The insert event payload.
|
|
233
|
+
*/
|
|
234
|
+
fieldArrayInsert<T extends FormValues>(
|
|
235
|
+
entity: FormEntity<T>,
|
|
236
|
+
payload: FieldArrayInsertPayload,
|
|
237
|
+
): void
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Moves an item in a field array from one index to another.
|
|
241
|
+
* @param entity The form entity.
|
|
242
|
+
* @param payload The move event payload.
|
|
243
|
+
*/
|
|
244
|
+
fieldArrayMove<T extends FormValues>(
|
|
245
|
+
entity: FormEntity<T>,
|
|
246
|
+
payload: FieldArrayMovePayload,
|
|
247
|
+
): void
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Handles a change in a form field's value and optionally validates it.
|
|
251
|
+
* @param entity The form entity.
|
|
252
|
+
* @param payload The change event payload.
|
|
253
|
+
*/
|
|
254
|
+
fieldChange<T extends FormValues>(
|
|
255
|
+
entity: FormEntity<T>,
|
|
256
|
+
payload: FormFieldChangePayload<T>,
|
|
257
|
+
): void
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Handles the blur event for a form field, marking it as touched and optionally validating it.
|
|
261
|
+
* @param entity The form entity.
|
|
262
|
+
* @param payload The blur event payload.
|
|
263
|
+
*/
|
|
264
|
+
fieldBlur<T extends FormValues>(
|
|
265
|
+
entity: FormEntity<T>,
|
|
266
|
+
payload: FormFieldBlurPayload,
|
|
267
|
+
): void
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Resets the form to its initial state.
|
|
271
|
+
* @param entity The form entity.
|
|
272
|
+
* @param payload The reset event payload.
|
|
273
|
+
*/
|
|
274
|
+
reset<T extends FormValues>(
|
|
275
|
+
entity: FormEntity<T>,
|
|
276
|
+
payload: FormResetPayload,
|
|
277
|
+
): void
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Synchronously validates the entire form.
|
|
281
|
+
* @param entity The form entity.
|
|
282
|
+
* @param payload The validation event payload.
|
|
283
|
+
*/
|
|
284
|
+
validate<T extends FormValues>(
|
|
285
|
+
entity: FormEntity<T>,
|
|
286
|
+
payload: FormValidatePayload<T>,
|
|
287
|
+
): void
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Asynchronously validates the entire form.
|
|
291
|
+
* Dispatches 'formValidationComplete' on success or 'formValidationError' on failure.
|
|
292
|
+
* @param entity The form entity.
|
|
293
|
+
* @param payload The async validation event payload.
|
|
294
|
+
* @param api The API object for notifying events.
|
|
295
|
+
*/
|
|
296
|
+
validateAsync<T extends FormValues>(
|
|
297
|
+
entity: FormEntity<T>,
|
|
298
|
+
payload: FormValidateAsyncPayload<T>,
|
|
299
|
+
api: any,
|
|
300
|
+
): Promise<void>
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Handles the completion of async validation.
|
|
304
|
+
* @param entity The form entity.
|
|
305
|
+
* @param payload The validation completion event payload.
|
|
306
|
+
*/
|
|
307
|
+
validationComplete<T extends FormValues>(
|
|
308
|
+
entity: FormEntity<T>,
|
|
309
|
+
payload: FormValidationCompletePayload<T>,
|
|
310
|
+
): void
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Handles validation errors from async validation.
|
|
314
|
+
* @param entity The form entity.
|
|
315
|
+
* @param payload The validation error event payload.
|
|
316
|
+
*/
|
|
317
|
+
validationError<T extends FormValues>(
|
|
318
|
+
entity: FormEntity<T>,
|
|
319
|
+
payload: FormValidationErrorPayload,
|
|
320
|
+
): void
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Gets the value of a specific field in the form.
|
|
325
|
+
* @param form The form entity.
|
|
326
|
+
* @param path The path to the field (e.g., 'user.name').
|
|
327
|
+
* @param defaultValue An optional default value to return if the path does not exist.
|
|
328
|
+
*/
|
|
329
|
+
export declare function getFieldValue<T extends FormValues>(
|
|
330
|
+
form: FormEntity<T>,
|
|
331
|
+
path: string,
|
|
332
|
+
defaultValue?: any,
|
|
333
|
+
): any
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Gets the error message for a specific field in the form.
|
|
337
|
+
* @param form The form entity.
|
|
338
|
+
* @param path The path to the field.
|
|
339
|
+
*/
|
|
340
|
+
export declare function getFieldError<T extends FormValues>(
|
|
341
|
+
form: FormEntity<T>,
|
|
342
|
+
path: string,
|
|
343
|
+
): string | null
|
|
344
|
+
|
|
345
|
+
/** Checks if a specific field has been touched by the user. */
|
|
346
|
+
export declare function isFieldTouched<T extends FormValues>(
|
|
347
|
+
form: FormEntity<T>,
|
|
348
|
+
path: string,
|
|
349
|
+
): boolean
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Checks if the form has any validation errors.
|
|
353
|
+
* @param errors The errors object from a form entity.
|
|
354
|
+
*/
|
|
355
|
+
export declare function hasErrors(errors: FormErrors<any>): boolean
|
|
356
|
+
|
|
357
|
+
/** Initializes a nested metadata structure (for errors or touched state) based on a value structure. */
|
|
358
|
+
export declare function initMetadata(value: any): any
|
|
359
|
+
|
|
360
|
+
/** Resets the form to its initial state. */
|
|
361
|
+
export declare function resetForm<T extends FormValues>(
|
|
362
|
+
form: FormEntity<T>,
|
|
363
|
+
): void
|
|
364
|
+
|
|
365
|
+
/** Sets the value of a specific field and marks it as touched. */
|
|
366
|
+
export declare function setFieldValue<T extends FormValues>(
|
|
367
|
+
form: FormEntity<T>,
|
|
368
|
+
path: string,
|
|
369
|
+
value: any,
|
|
370
|
+
): void
|
|
371
|
+
|
|
372
|
+
/** Sets an error message for a specific field. */
|
|
373
|
+
export declare function setFieldError<T extends FormValues>(
|
|
374
|
+
form: FormEntity<T>,
|
|
375
|
+
path: string,
|
|
376
|
+
error: string | null,
|
|
377
|
+
): void
|
package/types/index.d.ts
ADDED
package/types/mount.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { TemplateResult } from "lit-html"
|
|
2
|
+
import type { Store, Api as StoreApi } from "@inglorious/store"
|
|
3
|
+
|
|
4
|
+
export type Api = StoreApi & {
|
|
5
|
+
/**
|
|
6
|
+
* Renders a single entity by its ID.
|
|
7
|
+
* @param id The ID of the entity to render.
|
|
8
|
+
* @returns The rendered template or null.
|
|
9
|
+
*/
|
|
10
|
+
render: (id: string) => TemplateResult | null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Mounts a lit-html template to the DOM and subscribes to a store for re-rendering.
|
|
15
|
+
* @param store The application state store.
|
|
16
|
+
* @param renderFn The root render function that receives the API and returns a template.
|
|
17
|
+
* @param element The DOM element to mount the template to.
|
|
18
|
+
* @returns An unsubscribe function.
|
|
19
|
+
*/
|
|
20
|
+
export function mount(
|
|
21
|
+
store: Store,
|
|
22
|
+
renderFn: (api: Api) => TemplateResult | null,
|
|
23
|
+
element: HTMLElement | DocumentFragment,
|
|
24
|
+
): () => void
|