@leavepulse/ui 0.14.10 → 0.15.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/dist/chunks/{LpAppShell-BLKZDLh8.js → LpAppShell-CV194os6.js} +2 -2
- package/dist/chunks/{LpAutocomplete-B9uoCs4t.js → LpAutocomplete-DIgXmtCA.js} +13 -5
- package/dist/chunks/{LpConfirmDialog-BA45MvRL.js → LpConfirmDialog-WHkymac5.js} +1 -1
- package/dist/chunks/{LpDrawer-6n5Hd-iF.js → LpDrawer-BHzCy-Aa.js} +1 -1
- package/dist/chunks/{LpFileTree-0Ko_uj8q.js → LpFileTree-BQz8Rfgz.js} +55 -34
- package/dist/chunks/{LpFileTreeNode-D3lNEP-1.js → LpFileTreeNode-DFg4PhOK.js} +28 -14
- package/dist/chunks/{LpLogViewer-BBgaY38i.js → LpLogViewer-BvzRZPjs.js} +2 -1
- package/dist/chunks/{LpModal-B9CVVC5U.js → LpModal-DpBHQpbU.js} +1 -1
- package/dist/chunks/{LpNotificationBell-B9VHks_D.js → LpNotificationBell-BtfRoL2i.js} +1 -1
- package/dist/chunks/{LpScrollArea-BtnZCCBT.js → LpScrollArea-De9DLpa1.js} +2 -2
- package/dist/chunks/{LpSidebar-tZ2z-PnM.js → LpSidebar-B11IDRQi.js} +2 -2
- package/dist/chunks/{LpSidebarNav-D-YvtwsH.js → LpSidebarNav-TIUNHZX-.js} +1 -1
- package/dist/chunks/{LpTable-CzHVcr3v.js → LpTable-CKGaUbEM.js} +1 -1
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpAutocomplete.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.js +1 -1
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpFileTreeNode.vue.d.ts +3 -1
- package/dist/components/LpFileTreeNode.vue.js +1 -1
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpScrollArea.vue.d.ts +4 -2
- package/dist/components/LpScrollArea.vue.js +1 -1
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpTable.vue.js +1 -1
- package/dist/components/fileTree.d.ts +25 -0
- package/dist/components/fileTree.js +66 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +14 -12
- package/package.json +3 -2
- package/src/components/LpAutocomplete.vue +19 -2
- package/src/components/LpFileTree.vue +73 -35
- package/src/components/LpFileTreeNode.vue +35 -7
- package/src/components/LpLogViewer.vue +4 -0
- package/src/components/LpScrollArea.vue +16 -11
- package/src/components/fileTree.ts +100 -0
- package/src/index.ts +3 -1
|
@@ -196,6 +196,106 @@ export function checkStateOf(node: FileNode, checked: ReadonlySet<string>): Chec
|
|
|
196
196
|
return any ? "indeterminate" : "unchecked"
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
/**
|
|
200
|
+
* How much of a directory's subtree is ticked, counting FILES only (folders are
|
|
201
|
+
* containers, not payload). Lets a row say "3 / 12" so a half-filled checkbox
|
|
202
|
+
* isn't the only clue that a folder is partially selected.
|
|
203
|
+
*/
|
|
204
|
+
export function checkedCount(
|
|
205
|
+
node: FileNode,
|
|
206
|
+
checked: ReadonlySet<string>,
|
|
207
|
+
): { checked: number; total: number } {
|
|
208
|
+
let count = 0
|
|
209
|
+
let total = 0
|
|
210
|
+
function walk(list: FileNode[]) {
|
|
211
|
+
for (const child of list) {
|
|
212
|
+
if (child.kind === "file") {
|
|
213
|
+
total++
|
|
214
|
+
if (checked.has(child.id)) count++
|
|
215
|
+
} else if (child.children?.length) {
|
|
216
|
+
walk(child.children)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
walk(node.children ?? [])
|
|
221
|
+
return { checked: count, total }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Everything a row needs to render, precomputed once per node. */
|
|
225
|
+
export interface NodeStats {
|
|
226
|
+
state: CheckState
|
|
227
|
+
/** Ticked files in the subtree, and how many there are in total. */
|
|
228
|
+
checked: number
|
|
229
|
+
total: number
|
|
230
|
+
/** Rolled-up subtree size, or undefined when nothing carries one. */
|
|
231
|
+
size?: number
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Derive every row's state in ONE post-order pass over the tree, keyed by node
|
|
236
|
+
* id. Calling `checkStateOf`/`subtreeSize`/`checkedCount` per row instead makes
|
|
237
|
+
* each row walk its own subtree, which is quadratic on a deep tree and shows up
|
|
238
|
+
* as a stutter the moment a directory holds a few thousand files.
|
|
239
|
+
*/
|
|
240
|
+
export function computeStats(
|
|
241
|
+
nodes: FileNode[],
|
|
242
|
+
checked: ReadonlySet<string>,
|
|
243
|
+
): Map<string, NodeStats> {
|
|
244
|
+
const stats = new Map<string, NodeStats>()
|
|
245
|
+
|
|
246
|
+
function visit(node: FileNode): NodeStats {
|
|
247
|
+
if (node.kind === "file" || !node.children?.length) {
|
|
248
|
+
// A leaf, or a directory whose children aren't loaded: fall back to its
|
|
249
|
+
// own membership so an unexpanded folder can still be ticked whole.
|
|
250
|
+
const isChecked = checked.has(node.id)
|
|
251
|
+
const self: NodeStats = {
|
|
252
|
+
state: isChecked ? "checked" : "unchecked",
|
|
253
|
+
checked: node.kind === "file" && isChecked ? 1 : 0,
|
|
254
|
+
total: node.kind === "file" ? 1 : 0,
|
|
255
|
+
size: node.size,
|
|
256
|
+
}
|
|
257
|
+
stats.set(node.id, self)
|
|
258
|
+
return self
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
let all = true
|
|
262
|
+
let any = false
|
|
263
|
+
let count = 0
|
|
264
|
+
let total = 0
|
|
265
|
+
let bytes = 0
|
|
266
|
+
let sized = node.size !== undefined
|
|
267
|
+
|
|
268
|
+
for (const child of node.children) {
|
|
269
|
+
const childStats = visit(child)
|
|
270
|
+
if (childStats.state === "checked") any = true
|
|
271
|
+
else if (childStats.state === "indeterminate") {
|
|
272
|
+
any = true
|
|
273
|
+
all = false
|
|
274
|
+
} else all = false
|
|
275
|
+
count += childStats.checked
|
|
276
|
+
total += childStats.total
|
|
277
|
+
if (childStats.size !== undefined) {
|
|
278
|
+
bytes += childStats.size
|
|
279
|
+
sized = true
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const self: NodeStats = {
|
|
284
|
+
state: all ? "checked" : any ? "indeterminate" : "unchecked",
|
|
285
|
+
checked: count,
|
|
286
|
+
total,
|
|
287
|
+
// An explicit size on the directory wins: a server-side listing usually
|
|
288
|
+
// knows the real figure without the children being loaded.
|
|
289
|
+
size: node.size ?? (sized ? bytes : undefined),
|
|
290
|
+
}
|
|
291
|
+
stats.set(node.id, self)
|
|
292
|
+
return self
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
for (const node of nodes) visit(node)
|
|
296
|
+
return stats
|
|
297
|
+
}
|
|
298
|
+
|
|
199
299
|
/** Ids of every ancestor directory of `id`, so callers can reveal a deep node. */
|
|
200
300
|
export function ancestorIds(nodes: FileNode[], id: string): string[] {
|
|
201
301
|
const path: string[] = []
|
package/src/index.ts
CHANGED
|
@@ -27,10 +27,12 @@ export type { MenuItem } from "./components/LpDropdownMenu.vue"
|
|
|
27
27
|
export { default as LpEmptyState } from "./components/LpEmptyState.vue"
|
|
28
28
|
export { default as LpFileTree } from "./components/LpFileTree.vue"
|
|
29
29
|
export type { FileTreeSummary } from "./components/LpFileTree.vue"
|
|
30
|
-
export type { CheckState, FileNode } from "./components/fileTree"
|
|
30
|
+
export type { CheckState, FileNode, NodeStats } from "./components/fileTree"
|
|
31
31
|
export {
|
|
32
32
|
ancestorIds,
|
|
33
|
+
checkedCount,
|
|
33
34
|
checkStateOf,
|
|
35
|
+
computeStats,
|
|
34
36
|
fileIcon,
|
|
35
37
|
formatModified,
|
|
36
38
|
formatSize,
|