@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/README.md +22 -221
- package/package.json +5 -25
- package/src/form/handlers.js +332 -0
- package/src/form/helpers.js +32 -0
- package/src/form/index.js +4 -373
- package/src/hydrate.js +1 -0
- package/src/index.js +1 -0
- package/src/router/handlers.js +345 -0
- package/src/router/helpers.js +46 -0
- package/src/router/index.js +5 -384
- package/src/test.js +1 -1
- package/types/index.d.ts +0 -3
- package/src/list/index.js +0 -7
- package/src/list/logic.js +0 -75
- package/src/list/rendering.js +0 -105
- package/src/select/base.css +0 -52
- package/src/select/index.js +0 -7
- package/src/select/logic.js +0 -332
- package/src/select/rendering.js +0 -352
- package/src/select/theme.css +0 -133
- package/src/table/base.css +0 -34
- package/src/table/filters/date.js +0 -42
- package/src/table/filters/number.js +0 -22
- package/src/table/filters/range.js +0 -40
- package/src/table/filters/select.js +0 -34
- package/src/table/filters/text.js +0 -22
- package/src/table/filters.js +0 -24
- package/src/table/index.js +0 -7
- package/src/table/logic.js +0 -573
- package/src/table/rendering.js +0 -340
- package/src/table/theme.css +0 -83
- package/types/list.d.ts +0 -91
package/src/router/index.js
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* @typedef {import("../../types/router").RouterEntity} RouterEntity
|
|
4
|
-
* @typedef {import("../../types/router").Api} Api
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const SKIP_FULL_MATCH_GROUP = 1 // .match() result at index 0 is the full string
|
|
8
|
-
const REMOVE_COLON_PREFIX = 1
|
|
1
|
+
import { createHandlers } from "./handlers.js"
|
|
2
|
+
import { createHelpers } from "./helpers.js"
|
|
9
3
|
|
|
10
4
|
/**
|
|
11
5
|
* Route configuration map. Keys are route patterns (e.g. `/users/:id`) and
|
|
@@ -15,380 +9,7 @@ const REMOVE_COLON_PREFIX = 1
|
|
|
15
9
|
*/
|
|
16
10
|
const routeConfig = {}
|
|
17
11
|
|
|
18
|
-
|
|
19
|
-
* Guard to ensure global listeners are only attached once.
|
|
20
|
-
* @type {boolean}
|
|
21
|
-
*/
|
|
22
|
-
let areListenersInitialized = false
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Client-side router for entity-based systems. Handles URL changes, link interception, and browser history management.
|
|
26
|
-
* @type {RouterType}
|
|
27
|
-
*/
|
|
28
|
-
export const router = {
|
|
29
|
-
/**
|
|
30
|
-
* Initializes the router entity.
|
|
31
|
-
* Sets up the initial route and event listeners for navigation.
|
|
32
|
-
* @param {RouterEntity} entity - The router entity.
|
|
33
|
-
* @param {void} payload - Unused payload.
|
|
34
|
-
* @param {Api} api - The application API.
|
|
35
|
-
*/
|
|
36
|
-
init(entity, payload, api) {
|
|
37
|
-
// Handle initial route
|
|
38
|
-
const { pathname, search } = window.location
|
|
39
|
-
const initialPath = pathname + search
|
|
40
|
-
const route = findRoute(routeConfig, initialPath)
|
|
41
|
-
const entityId = entity.id
|
|
42
|
-
|
|
43
|
-
if (route) {
|
|
44
|
-
api.notify(`#${entityId}:navigate`, {
|
|
45
|
-
to: initialPath,
|
|
46
|
-
params: route.params,
|
|
47
|
-
replace: true,
|
|
48
|
-
})
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (areListenersInitialized) return
|
|
52
|
-
areListenersInitialized = true
|
|
53
|
-
|
|
54
|
-
// Listen for browser back/forward
|
|
55
|
-
window.addEventListener("popstate", () =>
|
|
56
|
-
api.notify(`#${entityId}:popstate`, payload),
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
// Intercept link clicks
|
|
60
|
-
document.addEventListener("click", (event) => {
|
|
61
|
-
// Find the closest <a> tag (handles clicks on children)
|
|
62
|
-
const link = event.target.closest("a")
|
|
63
|
-
|
|
64
|
-
if (!link) return
|
|
65
|
-
|
|
66
|
-
// Skip external links
|
|
67
|
-
if (link.target === "_blank") return
|
|
68
|
-
if (link.rel === "external") return
|
|
69
|
-
if (link.hasAttribute("data-external")) return
|
|
70
|
-
|
|
71
|
-
// Skip different origin links
|
|
72
|
-
if (link.origin !== window.location.origin) return
|
|
73
|
-
|
|
74
|
-
// Skip links with download attribute
|
|
75
|
-
if (link.hasAttribute("download")) return
|
|
76
|
-
|
|
77
|
-
// Skip mailto:, tel:, etc.
|
|
78
|
-
if (!["http:", "https:"].includes(link.protocol)) return
|
|
79
|
-
|
|
80
|
-
// Prevent default and use router
|
|
81
|
-
event.preventDefault()
|
|
82
|
-
|
|
83
|
-
const path = link.pathname + link.search + link.hash
|
|
84
|
-
api.notify(`#${entityId}:navigate`, path)
|
|
85
|
-
})
|
|
86
|
-
},
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Handles browser `popstate` events.
|
|
90
|
-
* Attempts to match the current location to a route and updates the router
|
|
91
|
-
* entity state. If the matched route is lazy (a loader function) it will
|
|
92
|
-
* attempt to load the module first.
|
|
93
|
-
* @param {RouterEntity} entity
|
|
94
|
-
* @param {Object} payload
|
|
95
|
-
* @param {Api} api
|
|
96
|
-
*/
|
|
97
|
-
async popstate(entity, payload, api) {
|
|
98
|
-
const path = window.location.pathname + window.location.search
|
|
99
|
-
const route = findRoute(routeConfig, path)
|
|
100
|
-
const entityId = entity.id
|
|
101
|
-
|
|
102
|
-
if (route) {
|
|
103
|
-
if (typeof route.entityType === "function") {
|
|
104
|
-
entity.isLoading = true
|
|
105
|
-
entity.error = null
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
const module = await route.entityType()
|
|
109
|
-
api.notify(`#${entityId}:routeLoadSuccess`, { module, route })
|
|
110
|
-
api.notify(`#${entityId}:popstate`, payload)
|
|
111
|
-
} catch (error) {
|
|
112
|
-
api.notify(`#${entityId}:routeLoadError`, { error, path })
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
updateRouter(entity, route)
|
|
119
|
-
|
|
120
|
-
api.notify("routeChange", {
|
|
121
|
-
route: entity.route,
|
|
122
|
-
params: entity.params,
|
|
123
|
-
query: entity.query,
|
|
124
|
-
path: entity.path,
|
|
125
|
-
})
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Handles navigation to a new route.
|
|
131
|
-
* @param {RouterEntity} entity - The router entity.
|
|
132
|
-
* @param {string|number|{to: string, params?: object, replace?: boolean, state?: object}} payload - The navigation target or options.
|
|
133
|
-
* @param {Api} api - The application API.
|
|
134
|
-
*/
|
|
135
|
-
async navigate(entity, payload, api) {
|
|
136
|
-
const options = ["number", "string"].includes(typeof payload)
|
|
137
|
-
? { to: payload }
|
|
138
|
-
: payload
|
|
139
|
-
const { to, params, replace, force, state = {} } = options
|
|
140
|
-
|
|
141
|
-
// Numeric navigation (back/forward)
|
|
142
|
-
if (typeof to === "number") {
|
|
143
|
-
history.go(to)
|
|
144
|
-
return
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Build final path
|
|
148
|
-
let path = to
|
|
149
|
-
|
|
150
|
-
// If params provided and "to" looks like a pattern, build the path
|
|
151
|
-
if (params && to.includes(":")) {
|
|
152
|
-
path = buildPath(to, params)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// If "to" is already a final path (like "/users/1"), use it directly
|
|
156
|
-
// The router will match it against patterns in routeConfig
|
|
157
|
-
|
|
158
|
-
const route = findRoute(routeConfig, path)
|
|
159
|
-
const entityId = entity.id
|
|
160
|
-
|
|
161
|
-
if (!route) {
|
|
162
|
-
console.warn(`No route matches path: ${path}`)
|
|
163
|
-
return
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (!force) {
|
|
167
|
-
// Prevent navigation if the full path (including query/hash) is identical.
|
|
168
|
-
const currentFullPath =
|
|
169
|
-
entity.path + window.location.search + window.location.hash
|
|
170
|
-
if (path === currentFullPath) {
|
|
171
|
-
return
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Asynchronous navigation
|
|
176
|
-
if (typeof route.entityType === "function") {
|
|
177
|
-
entity.isLoading = true
|
|
178
|
-
entity.error = null
|
|
179
|
-
|
|
180
|
-
try {
|
|
181
|
-
const module = await route.entityType()
|
|
182
|
-
api.notify(`#${entityId}:routeLoadSuccess`, { module, route })
|
|
183
|
-
api.notify(`#${entityId}:navigate`, payload)
|
|
184
|
-
} catch (error) {
|
|
185
|
-
api.notify(`#${entityId}:routeLoadError`, { error, path })
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
updateRouter(entity, route)
|
|
192
|
-
|
|
193
|
-
// Prepare history state
|
|
194
|
-
const historyState = {
|
|
195
|
-
...state,
|
|
196
|
-
route: entity.route,
|
|
197
|
-
params: entity.params,
|
|
198
|
-
query: entity.query,
|
|
199
|
-
path: entity.path,
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Navigate
|
|
203
|
-
const method = replace ? "replaceState" : "pushState"
|
|
204
|
-
history[method](historyState, "", path)
|
|
205
|
-
|
|
206
|
-
api.notify("routeChange", historyState)
|
|
207
|
-
},
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Handles successful loading of a lazily-loaded route module.
|
|
211
|
-
* Registers the loaded type in the runtime type registry via `api.setType`
|
|
212
|
-
* and updates the `routeConfig` entry for the pattern.
|
|
213
|
-
* @param {RouterEntity} entity
|
|
214
|
-
* @param {{module: object, route: {pattern: string, entityType: string}}} payload
|
|
215
|
-
* @param {Api} api
|
|
216
|
-
*/
|
|
217
|
-
routeLoadSuccess(entity, payload, api) {
|
|
218
|
-
const { module, route } = payload
|
|
219
|
-
|
|
220
|
-
const [typeName, type] = Object.entries(module).find(
|
|
221
|
-
([, type]) => type?.render,
|
|
222
|
-
)
|
|
12
|
+
export const router = { ...createHandlers(routeConfig) }
|
|
223
13
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
entity.isLoading = false
|
|
228
|
-
},
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Handles errors during lazy route loading.
|
|
232
|
-
* @param {RouterEntity} entity - The router entity.
|
|
233
|
-
* @param {{error: Error, path: string}} payload - The error payload.
|
|
234
|
-
*/
|
|
235
|
-
routeLoadError(entity, payload) {
|
|
236
|
-
const { error, path } = payload
|
|
237
|
-
console.error(`Failed to load route ${path}:`, error)
|
|
238
|
-
entity.path = path
|
|
239
|
-
entity.isLoading = false
|
|
240
|
-
entity.error = error
|
|
241
|
-
},
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Retrieves the current route configuration.
|
|
246
|
-
* @returns {Record<string, string|function>} The current route configuration.
|
|
247
|
-
*/
|
|
248
|
-
export function getRoutes() {
|
|
249
|
-
return routeConfig
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Retrieves a single route configuration given its path.
|
|
254
|
-
* @param {string} path - The path of the route to retrieve.
|
|
255
|
-
* @returns {string|function|undefined} The route configuration or undefined if not found.
|
|
256
|
-
*/
|
|
257
|
-
export function getRoute(path) {
|
|
258
|
-
return routeConfig[path]
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Sets or updates routes in the route configuration.
|
|
263
|
-
* Can be used both during initialization and at any point to add or update routes dynamically.
|
|
264
|
-
* @param {Record<string, string|function>} routes - An object mapping route paths/patterns to entity type names or loader functions.
|
|
265
|
-
*/
|
|
266
|
-
export function setRoutes(routes) {
|
|
267
|
-
Object.assign(routeConfig, routes)
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Adds a single route to the route configuration.
|
|
272
|
-
* @param {string} path - The route path or pattern (e.g., "/users/:userId").
|
|
273
|
-
* @param {string|function} route - The entity type name or a function that dynamically loads it.
|
|
274
|
-
*/
|
|
275
|
-
export function addRoute(path, route) {
|
|
276
|
-
routeConfig[path] = route
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Removes a route from the route configuration.
|
|
281
|
-
* @param {string} path - The route path or pattern to remove.
|
|
282
|
-
*/
|
|
283
|
-
export function removeRoute(path) {
|
|
284
|
-
delete routeConfig[path]
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Builds a URL path by substituting parameters into a route pattern.
|
|
289
|
-
* @param {string} pattern - The route pattern (e.g., "/users/:userId").
|
|
290
|
-
* @param {Record<string, string>} [params={}] - The parameters to substitute.
|
|
291
|
-
* @returns {string} The constructed path.
|
|
292
|
-
* @example
|
|
293
|
-
* buildPath("/users/:userId", { userId: "123" }) // returns "/users/123"
|
|
294
|
-
*/
|
|
295
|
-
function buildPath(pattern, params = {}) {
|
|
296
|
-
let path = pattern
|
|
297
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
298
|
-
path = path.replace(`:${key}`, value)
|
|
299
|
-
})
|
|
300
|
-
return path
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Finds a matching route configuration for a given URL path.
|
|
305
|
-
* It supports parameterized routes and a fallback "*" route.
|
|
306
|
-
* @param {Record<string, string>} routeConfig - The routes configuration map.
|
|
307
|
-
* @param {string} pathname - The URL path to match.
|
|
308
|
-
* @returns {{pattern: string, entityType: string, params: Record<string, string>, path: string}|null}
|
|
309
|
-
* The matched route object or null if no match is found.
|
|
310
|
-
*/
|
|
311
|
-
function findRoute(routeConfig, pathname) {
|
|
312
|
-
const [path, search] = pathname.split("?")
|
|
313
|
-
let fallbackRoute = null
|
|
314
|
-
|
|
315
|
-
for (const [pattern, entityType] of Object.entries(routeConfig)) {
|
|
316
|
-
if (pattern === "*") {
|
|
317
|
-
fallbackRoute = { pattern, entityType, params: {}, path }
|
|
318
|
-
continue
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const params = matchRoute(pattern, path)
|
|
322
|
-
if (params !== null) {
|
|
323
|
-
const query = search
|
|
324
|
-
? Object.fromEntries(new URLSearchParams(search))
|
|
325
|
-
: {}
|
|
326
|
-
return { pattern, entityType, params, path, query }
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
return fallbackRoute
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Matches a URL path against a route pattern and extracts any parameters.
|
|
335
|
-
* @param {string} pattern - The route pattern (e.g., "/users/:userId").
|
|
336
|
-
* @param {string} path - The URL path to match (e.g., "/users/123").
|
|
337
|
-
* @returns {Record<string, string>|null} An object of extracted parameters,
|
|
338
|
-
* or null if the path does not match the pattern.
|
|
339
|
-
*/
|
|
340
|
-
function matchRoute(pattern, path) {
|
|
341
|
-
const paramNames = getParamNames(pattern)
|
|
342
|
-
const regex = patternToRegex(pattern)
|
|
343
|
-
const match = path.match(regex)
|
|
344
|
-
|
|
345
|
-
if (!match) return null
|
|
346
|
-
|
|
347
|
-
const params = {}
|
|
348
|
-
paramNames.forEach((name, i) => {
|
|
349
|
-
params[name] = match[i + SKIP_FULL_MATCH_GROUP]
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
return params
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Parses a route pattern and extracts the names of its parameters.
|
|
357
|
-
* @param {string} pattern - The route pattern.
|
|
358
|
-
* @returns {string[]} An array of parameter names.
|
|
359
|
-
* @example
|
|
360
|
-
* getParamNames("/users/:userId/posts/:postId") // returns ["userId", "postId"]
|
|
361
|
-
*/
|
|
362
|
-
function getParamNames(pattern) {
|
|
363
|
-
const matches = pattern.match(/:(\w+)/g)
|
|
364
|
-
return matches ? matches.map((m) => m.slice(REMOVE_COLON_PREFIX)) : []
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Converts a route pattern into a regular expression for matching URL paths.
|
|
369
|
-
* @param {string} pattern - The route pattern (e.g., "/users/:userId").
|
|
370
|
-
* @returns {RegExp} The corresponding regular expression.
|
|
371
|
-
*/
|
|
372
|
-
function patternToRegex(pattern) {
|
|
373
|
-
const regexPattern = pattern
|
|
374
|
-
.replace(/\//g, "\\/")
|
|
375
|
-
.replace(/:(\w+)/g, "([^\\/]+)")
|
|
376
|
-
return new RegExp(`^${regexPattern}$`)
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Updates the router entity's internal state.
|
|
381
|
-
* @param {RouterEntity} entity - The router entity.
|
|
382
|
-
* @param {object} options - The update options.
|
|
383
|
-
* @param {string} options.entityType - The matched entity type.
|
|
384
|
-
* @param {string} options.path - The full path (pathname only, no query).
|
|
385
|
-
* @param {object} options.params - The extracted route parameters.
|
|
386
|
-
* @param {object} [options.query] - The parsed query parameters.
|
|
387
|
-
*/
|
|
388
|
-
function updateRouter(entity, { entityType, path, params, query }) {
|
|
389
|
-
entity.route = entityType
|
|
390
|
-
entity.path = path
|
|
391
|
-
entity.params = params
|
|
392
|
-
entity.query = query
|
|
393
|
-
entity.hash = window.location.hash
|
|
394
|
-
}
|
|
14
|
+
export const { getRoutes, getRoute, setRoutes, addRoute, removeRoute } =
|
|
15
|
+
createHelpers(routeConfig)
|
package/src/test.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { trigger } from "@inglorious/store/test"
|
|
1
|
+
export { createMockApi, trigger } from "@inglorious/store/test"
|
|
2
2
|
export { render } from "lit-html"
|
package/types/index.d.ts
CHANGED
package/src/list/index.js
DELETED
package/src/list/logic.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {import('../../types/list').ListEntity} ListEntity
|
|
3
|
-
* @typedef {import('../../types/mount').Api} Api
|
|
4
|
-
* @typedef {import('lit-html').TemplateResult} TemplateResult
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const LIST_START = 0
|
|
8
|
-
|
|
9
|
-
export const logic = {
|
|
10
|
-
/**
|
|
11
|
-
* Initializes the list entity with default state.
|
|
12
|
-
* @param {ListEntity} entity
|
|
13
|
-
*/
|
|
14
|
-
create(entity) {
|
|
15
|
-
resetList(entity)
|
|
16
|
-
},
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Handles the scroll event to update the visible range.
|
|
20
|
-
* @param {ListEntity} entity
|
|
21
|
-
* @param {HTMLElement} containerEl
|
|
22
|
-
*/
|
|
23
|
-
scroll(entity, containerEl) {
|
|
24
|
-
const scrollTop = containerEl.scrollTop
|
|
25
|
-
const { items, bufferSize, itemHeight, estimatedHeight, viewportHeight } =
|
|
26
|
-
entity
|
|
27
|
-
const height = itemHeight || estimatedHeight
|
|
28
|
-
|
|
29
|
-
const start = Math.max(
|
|
30
|
-
LIST_START,
|
|
31
|
-
Math.floor(scrollTop / height) - bufferSize,
|
|
32
|
-
)
|
|
33
|
-
const visibleCount = Math.ceil(viewportHeight / height)
|
|
34
|
-
const end = Math.min(start + visibleCount + bufferSize, items.length)
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
entity.visibleRange.start === start &&
|
|
38
|
-
entity.visibleRange.end === end
|
|
39
|
-
) {
|
|
40
|
-
return
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
entity.scrollTop = scrollTop
|
|
44
|
-
entity.visibleRange = { start, end }
|
|
45
|
-
},
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Mounts the list, measuring the first item to determine item height.
|
|
49
|
-
* @param {ListEntity} entity
|
|
50
|
-
* @param {HTMLElement} containerEl
|
|
51
|
-
*/
|
|
52
|
-
mount(entity, containerEl) {
|
|
53
|
-
const firstItem = containerEl.querySelector("[data-index]")
|
|
54
|
-
if (!firstItem) return
|
|
55
|
-
|
|
56
|
-
entity.itemHeight = firstItem.offsetHeight
|
|
57
|
-
entity.visibleRange = {
|
|
58
|
-
start: 0,
|
|
59
|
-
end: Math.ceil(entity.viewportHeight / entity.itemHeight),
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Resets the list entity state.
|
|
66
|
-
* @param {ListEntity} entity
|
|
67
|
-
*/
|
|
68
|
-
function resetList(entity) {
|
|
69
|
-
entity.scrollTop = 0
|
|
70
|
-
entity.visibleRange ??= { start: 0, end: 20 }
|
|
71
|
-
entity.viewportHeight ??= 600
|
|
72
|
-
entity.bufferSize ??= 5
|
|
73
|
-
entity.itemHeight ??= null
|
|
74
|
-
entity.estimatedHeight ??= 50
|
|
75
|
-
}
|
package/src/list/rendering.js
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {import('../../types/mount').Api} Api
|
|
3
|
-
* @typedef {import('../../types/list').ListEntity} ListEntity
|
|
4
|
-
* @typedef {import('lit-html').TemplateResult} TemplateResult
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { html } from "lit-html"
|
|
8
|
-
import { ref } from "lit-html/directives/ref.js"
|
|
9
|
-
import { repeat } from "lit-html/directives/repeat.js"
|
|
10
|
-
import { styleMap } from "lit-html/directives/style-map.js"
|
|
11
|
-
|
|
12
|
-
const PRETTY_INDEX = 1
|
|
13
|
-
/**
|
|
14
|
-
* Rendering logic for the virtualized list component.
|
|
15
|
-
*/
|
|
16
|
-
export const rendering = {
|
|
17
|
-
/**
|
|
18
|
-
* Renders the virtualized list component.
|
|
19
|
-
* @param {ListEntity} entity The list entity state.
|
|
20
|
-
* @param {Api} api The API object.
|
|
21
|
-
* @returns {TemplateResult} The rendered list.
|
|
22
|
-
*/
|
|
23
|
-
render(entity, api) {
|
|
24
|
-
const { items, visibleRange, viewportHeight, itemHeight, estimatedHeight } =
|
|
25
|
-
entity
|
|
26
|
-
const type = api.getType(entity.type)
|
|
27
|
-
|
|
28
|
-
if (!items) {
|
|
29
|
-
console.warn(`list entity ${entity.id} needs 'items'`)
|
|
30
|
-
return html``
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (!type.renderItem) {
|
|
34
|
-
console.warn(`type ${entity.type} needs 'renderItem' method`)
|
|
35
|
-
return html``
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const visibleItems = items.slice(visibleRange.start, visibleRange.end)
|
|
39
|
-
const height = itemHeight || estimatedHeight
|
|
40
|
-
const totalHeight = items.length * height
|
|
41
|
-
|
|
42
|
-
return html`
|
|
43
|
-
<div
|
|
44
|
-
style=${styleMap({ height: `${viewportHeight}px`, overflow: "auto" })}
|
|
45
|
-
@scroll=${(e) => api.notify(`#${entity.id}:scroll`, e.target)}
|
|
46
|
-
${ref((el) => {
|
|
47
|
-
if (el && !itemHeight) {
|
|
48
|
-
queueMicrotask(() => {
|
|
49
|
-
api.notify(`#${entity.id}:mount`, el)
|
|
50
|
-
})
|
|
51
|
-
}
|
|
52
|
-
})}
|
|
53
|
-
>
|
|
54
|
-
<div
|
|
55
|
-
style=${styleMap({
|
|
56
|
-
height: `${totalHeight}px`,
|
|
57
|
-
position: "relative",
|
|
58
|
-
})}
|
|
59
|
-
>
|
|
60
|
-
${repeat(
|
|
61
|
-
visibleItems,
|
|
62
|
-
(item) => item.id,
|
|
63
|
-
(item, index) => {
|
|
64
|
-
const absoluteIndex = visibleRange.start + index
|
|
65
|
-
const top = absoluteIndex * height
|
|
66
|
-
|
|
67
|
-
return html`
|
|
68
|
-
<div
|
|
69
|
-
style=${styleMap({
|
|
70
|
-
position: "absolute",
|
|
71
|
-
top: `${top}px`,
|
|
72
|
-
width: "100%",
|
|
73
|
-
})}
|
|
74
|
-
data-index=${absoluteIndex}
|
|
75
|
-
>
|
|
76
|
-
${type.renderItem(
|
|
77
|
-
entity,
|
|
78
|
-
{ item, index: absoluteIndex },
|
|
79
|
-
api,
|
|
80
|
-
)}
|
|
81
|
-
</div>
|
|
82
|
-
`
|
|
83
|
-
},
|
|
84
|
-
)}
|
|
85
|
-
</div>
|
|
86
|
-
</div>
|
|
87
|
-
`
|
|
88
|
-
},
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Default item renderer.
|
|
92
|
-
* @param {ListEntity} entity The list entity.
|
|
93
|
-
* @param {object} payload The payload for rendering the item.
|
|
94
|
-
* @param {any} payload.item The item data.
|
|
95
|
-
* @param {number} payload.index The item's absolute index in the full list.
|
|
96
|
-
* @param {Api} api The API object.
|
|
97
|
-
* @returns {TemplateResult} The rendered list item.
|
|
98
|
-
*/
|
|
99
|
-
// eslint-disable-next-line no-unused-vars
|
|
100
|
-
renderItem(entity, { item, index }, api) {
|
|
101
|
-
return html`<div class="iw-list-item">
|
|
102
|
-
${index + PRETTY_INDEX}. ${JSON.stringify(item)}
|
|
103
|
-
</div>`
|
|
104
|
-
},
|
|
105
|
-
}
|
package/src/select/base.css
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
.iw-select {
|
|
2
|
-
position: relative;
|
|
3
|
-
display: inline-block;
|
|
4
|
-
width: 100%;
|
|
5
|
-
|
|
6
|
-
.iw-select-control {
|
|
7
|
-
display: flex;
|
|
8
|
-
align-items: center;
|
|
9
|
-
|
|
10
|
-
cursor: pointer;
|
|
11
|
-
user-select: none;
|
|
12
|
-
|
|
13
|
-
.iw-select-value,
|
|
14
|
-
.iw-select-placeholder,
|
|
15
|
-
.iw-select-multi-value {
|
|
16
|
-
flex: 1;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
.iw-select-multi-value {
|
|
20
|
-
display: flex;
|
|
21
|
-
flex-wrap: wrap;
|
|
22
|
-
|
|
23
|
-
.iw-select-multi-value-tag {
|
|
24
|
-
display: flex;
|
|
25
|
-
align-items: center;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.iw-select-dropdown {
|
|
31
|
-
position: absolute;
|
|
32
|
-
top: 100%;
|
|
33
|
-
left: 0;
|
|
34
|
-
right: 0;
|
|
35
|
-
z-index: 1000;
|
|
36
|
-
display: flex;
|
|
37
|
-
flex-direction: column;
|
|
38
|
-
|
|
39
|
-
.iw-select-dropdown-search {
|
|
40
|
-
width: 100%;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.iw-select-dropdown-options {
|
|
44
|
-
.iw-select-dropdown-options-option {
|
|
45
|
-
display: flex;
|
|
46
|
-
align-items: center;
|
|
47
|
-
cursor: pointer;
|
|
48
|
-
user-select: none;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|