@motor-cms/ui-admin 4.10.1 → 4.12.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.
|
@@ -10,6 +10,10 @@ interface CategoryItem {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
const TEMP_NEW_ID = -1
|
|
13
|
+
// Horizontal pixels per nesting level. Dragging the item this far to the
|
|
14
|
+
// right/left while moving changes the target depth by one — this is what makes
|
|
15
|
+
// the nesting level directly visible and controllable.
|
|
16
|
+
const INDENT = 24
|
|
13
17
|
|
|
14
18
|
const props = defineProps<{
|
|
15
19
|
treeId: string
|
|
@@ -38,7 +42,6 @@ const activeItemId = computed(() => props.currentId ?? (isCreateMode.value ? TEM
|
|
|
38
42
|
|
|
39
43
|
// The parent_id of root-level categories (their parent is outside the returned list)
|
|
40
44
|
const rootParentId = ref<number | null>(null)
|
|
41
|
-
const isDragging = ref(false)
|
|
42
45
|
const expandedIds = ref(new Set<number>())
|
|
43
46
|
|
|
44
47
|
// IDs that can't be a drop/select target (current item + its descendants)
|
|
@@ -84,7 +87,6 @@ function buildTree(cats: CategoryItem[]): TreeNode[] {
|
|
|
84
87
|
map.get(cat.parent_id)!.children.push(node)
|
|
85
88
|
} else {
|
|
86
89
|
roots.push(node)
|
|
87
|
-
// Capture the parent_id of root-level categories
|
|
88
90
|
if (rootParentId.value === null && cat.parent_id !== null) {
|
|
89
91
|
rootParentId.value = cat.parent_id
|
|
90
92
|
}
|
|
@@ -93,7 +95,6 @@ function buildTree(cats: CategoryItem[]): TreeNode[] {
|
|
|
93
95
|
return roots
|
|
94
96
|
}
|
|
95
97
|
|
|
96
|
-
// Find ancestor IDs from root to target (not including target)
|
|
97
98
|
function findPathTo(nodes: TreeNode[], targetId: number): number[] | null {
|
|
98
99
|
for (const node of nodes) {
|
|
99
100
|
if (node.id === targetId) return []
|
|
@@ -108,15 +109,12 @@ function findPathTo(nodes: TreeNode[], targetId: number): number[] | null {
|
|
|
108
109
|
watch(categories, (cats) => {
|
|
109
110
|
tree.value = buildTree(cats)
|
|
110
111
|
|
|
111
|
-
// Empty tree: default rootParentId to the tree's own ID (root node)
|
|
112
112
|
if (rootParentId.value === null) {
|
|
113
113
|
rootParentId.value = Number(props.treeId)
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
// Create mode: insert virtual "new" node at the start of the root list
|
|
117
116
|
if (isCreateMode.value) {
|
|
118
117
|
tree.value.unshift({ id: TEMP_NEW_ID, name: props.newItemName || '', children: [] })
|
|
119
|
-
// Emit initial position (first child at root level)
|
|
120
118
|
emitPosition()
|
|
121
119
|
}
|
|
122
120
|
|
|
@@ -129,7 +127,6 @@ watch(categories, (cats) => {
|
|
|
129
127
|
}
|
|
130
128
|
}, { immediate: true })
|
|
131
129
|
|
|
132
|
-
// Keep the active node's name in sync with the prop
|
|
133
130
|
watch(() => props.newItemName, (name) => {
|
|
134
131
|
const id = activeItemId.value
|
|
135
132
|
if (id === undefined) return
|
|
@@ -139,16 +136,13 @@ watch(() => props.newItemName, (name) => {
|
|
|
139
136
|
|
|
140
137
|
function toggleNode(id: number) {
|
|
141
138
|
const next = new Set(expandedIds.value)
|
|
142
|
-
if (next.has(id))
|
|
143
|
-
|
|
144
|
-
} else {
|
|
145
|
-
next.add(id)
|
|
146
|
-
}
|
|
139
|
+
if (next.has(id)) next.delete(id)
|
|
140
|
+
else next.add(id)
|
|
147
141
|
expandedIds.value = next
|
|
148
142
|
}
|
|
149
143
|
|
|
150
144
|
// =============================================
|
|
151
|
-
// Tree
|
|
145
|
+
// Tree data helpers (move + emit position)
|
|
152
146
|
// =============================================
|
|
153
147
|
|
|
154
148
|
function findAndRemove(nodes: TreeNode[], id: number): TreeNode | null {
|
|
@@ -207,7 +201,6 @@ function moveItem(itemId: number, newParentId: number | null, newIndex: number)
|
|
|
207
201
|
|
|
208
202
|
targetList.splice(newIndex, 0, item)
|
|
209
203
|
|
|
210
|
-
// Auto-expand the target parent so the user sees the result
|
|
211
204
|
if (newParentId !== null && !expandedIds.value.has(newParentId)) {
|
|
212
205
|
const next = new Set(expandedIds.value)
|
|
213
206
|
next.add(newParentId)
|
|
@@ -218,114 +211,164 @@ function moveItem(itemId: number, newParentId: number | null, newIndex: number)
|
|
|
218
211
|
}
|
|
219
212
|
|
|
220
213
|
// =============================================
|
|
221
|
-
//
|
|
214
|
+
// Flatten the visible tree into a single list with depth
|
|
222
215
|
// =============================================
|
|
223
216
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
let current = catMap.get(nodeId)
|
|
231
|
-
while (current) {
|
|
232
|
-
if (current.parent_id === ancestorId) return true
|
|
233
|
-
if (current.parent_id === null) return false
|
|
234
|
-
current = current.parent_id !== null ? catMap.get(current.parent_id) : undefined
|
|
235
|
-
}
|
|
236
|
-
return false
|
|
217
|
+
interface FlatItem {
|
|
218
|
+
id: number
|
|
219
|
+
name: string
|
|
220
|
+
depth: number
|
|
221
|
+
parentId: number | null
|
|
222
|
+
hasChildren: boolean
|
|
237
223
|
}
|
|
238
224
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (toRemove.length === 0) return
|
|
247
|
-
const next = new Set(expandedIds.value)
|
|
248
|
-
for (const id of toRemove) {
|
|
249
|
-
next.delete(id)
|
|
250
|
-
autoExpandedIds.delete(id)
|
|
225
|
+
const flattened = computed<FlatItem[]>(() => {
|
|
226
|
+
const out: FlatItem[] = []
|
|
227
|
+
function walk(nodes: TreeNode[], depth: number, parentId: number | null) {
|
|
228
|
+
for (const n of nodes) {
|
|
229
|
+
out.push({ id: n.id, name: n.name, depth, parentId, hasChildren: n.children.length > 0 })
|
|
230
|
+
if (n.children.length && expandedIds.value.has(n.id)) walk(n.children, depth + 1, n.id)
|
|
231
|
+
}
|
|
251
232
|
}
|
|
252
|
-
|
|
253
|
-
|
|
233
|
+
walk(tree.value, 0, rootParentId.value)
|
|
234
|
+
return out
|
|
235
|
+
})
|
|
254
236
|
|
|
255
|
-
function
|
|
256
|
-
|
|
257
|
-
clearTimeout(hoverTimer)
|
|
258
|
-
hoverTimer = null
|
|
259
|
-
hoverTargetId = null
|
|
260
|
-
}
|
|
237
|
+
function isExpanded(id: number): boolean {
|
|
238
|
+
return expandedIds.value.has(id)
|
|
261
239
|
}
|
|
262
240
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
hoverTargetId = id
|
|
267
|
-
hoverTimer = setTimeout(() => {
|
|
268
|
-
const next = new Set(expandedIds.value)
|
|
269
|
-
next.add(id)
|
|
270
|
-
expandedIds.value = next
|
|
271
|
-
autoExpandedIds.add(id)
|
|
272
|
-
hoverTargetId = null
|
|
273
|
-
hoverTimer = null
|
|
274
|
-
}, 500)
|
|
275
|
-
}
|
|
241
|
+
// =============================================
|
|
242
|
+
// Pointer-based drag with horizontal level control
|
|
243
|
+
// =============================================
|
|
276
244
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
245
|
+
const containerRef = ref<HTMLElement | null>(null)
|
|
246
|
+
const dragActive = ref(false)
|
|
247
|
+
const isDragging = computed(() => dragActive.value)
|
|
248
|
+
const pointer = ref({ x: 0, y: 0 })
|
|
249
|
+
let startX = 0
|
|
250
|
+
let activeDepth = 0
|
|
280
251
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
252
|
+
// Item above the insertion point (null = insert at very top)
|
|
253
|
+
const overItemId = ref<number | null>(null)
|
|
254
|
+
const offsetLeft = ref(0)
|
|
255
|
+
|
|
256
|
+
// Descendants of the active item — excluded from the list while dragging
|
|
257
|
+
const activeDescendantIds = computed<Set<number>>(() => {
|
|
258
|
+
const ids = new Set<number>()
|
|
259
|
+
if (activeItemId.value === undefined) return ids
|
|
260
|
+
const node = findNode(tree.value, activeItemId.value)
|
|
261
|
+
if (node) {
|
|
262
|
+
(function collect(n: TreeNode) {
|
|
263
|
+
for (const c of n.children) { ids.add(c.id); collect(c) }
|
|
264
|
+
})(node)
|
|
292
265
|
}
|
|
266
|
+
return ids
|
|
267
|
+
})
|
|
293
268
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
269
|
+
// What we render: during drag the active item + its subtree lift out
|
|
270
|
+
const displayItems = computed<FlatItem[]>(() => {
|
|
271
|
+
if (!dragActive.value) return flattened.value
|
|
272
|
+
return flattened.value.filter(i => i.id !== activeItemId.value && !activeDescendantIds.value.has(i.id))
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
const activeItem = computed(() => flattened.value.find(i => i.id === activeItemId.value) ?? null)
|
|
276
|
+
|
|
277
|
+
const projected = computed(() => {
|
|
278
|
+
if (!dragActive.value) return null
|
|
279
|
+
const items = displayItems.value
|
|
280
|
+
const overIndex = overItemId.value === null ? -1 : items.findIndex(i => i.id === overItemId.value)
|
|
281
|
+
const prevItem = overIndex >= 0 ? items[overIndex] : null
|
|
282
|
+
const nextItem = items[overIndex + 1] ?? null
|
|
283
|
+
|
|
284
|
+
const dragDepth = Math.round(offsetLeft.value / INDENT)
|
|
285
|
+
const projectedDepth = activeDepth + dragDepth
|
|
286
|
+
const maxDepth = prevItem ? prevItem.depth + 1 : 0
|
|
287
|
+
const minDepth = nextItem ? nextItem.depth : 0
|
|
288
|
+
const depth = Math.max(minDepth, Math.min(projectedDepth, maxDepth))
|
|
289
|
+
|
|
290
|
+
let parentId: number | null
|
|
291
|
+
if (depth === 0 || !prevItem) {
|
|
292
|
+
parentId = rootParentId.value
|
|
293
|
+
} else if (depth === prevItem.depth) {
|
|
294
|
+
parentId = prevItem.parentId
|
|
295
|
+
} else if (depth > prevItem.depth) {
|
|
296
|
+
parentId = prevItem.id
|
|
297
|
+
} else {
|
|
298
|
+
const ancestor = items.slice(0, overIndex + 1).reverse().find(i => i.depth === depth)
|
|
299
|
+
parentId = ancestor?.parentId ?? rootParentId.value
|
|
298
300
|
}
|
|
299
301
|
|
|
300
|
-
|
|
302
|
+
return { depth, parentId, overIndex, overItemId: overItemId.value }
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
const projectedParentName = computed(() => {
|
|
306
|
+
const p = projected.value
|
|
307
|
+
if (!p) return null
|
|
308
|
+
if (p.parentId === null || p.parentId === rootParentId.value) return null
|
|
309
|
+
return findNode(tree.value, p.parentId)?.name ?? null
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
function rowsInDom(): HTMLElement[] {
|
|
313
|
+
return [...(containerRef.value?.querySelectorAll<HTMLElement>('[data-flat-id]') ?? [])]
|
|
301
314
|
}
|
|
302
315
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
if (autoExpandedIds.size === 0) return
|
|
310
|
-
const next = new Set(expandedIds.value)
|
|
311
|
-
for (const id of autoExpandedIds) {
|
|
312
|
-
next.delete(id)
|
|
316
|
+
function updateOver(clientY: number) {
|
|
317
|
+
let over: number | null = null
|
|
318
|
+
for (const row of rowsInDom()) {
|
|
319
|
+
const r = row.getBoundingClientRect()
|
|
320
|
+
if (clientY >= r.top + r.height / 2) over = Number(row.dataset.flatId)
|
|
321
|
+
else break
|
|
313
322
|
}
|
|
314
|
-
|
|
315
|
-
|
|
323
|
+
overItemId.value = over
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function onHandlePointerDown(e: PointerEvent) {
|
|
327
|
+
if (activeItemId.value === undefined) return
|
|
328
|
+
e.preventDefault()
|
|
329
|
+
e.stopPropagation()
|
|
330
|
+
dragActive.value = true
|
|
331
|
+
startX = e.clientX
|
|
332
|
+
offsetLeft.value = 0
|
|
333
|
+
activeDepth = activeItem.value?.depth ?? 0
|
|
334
|
+
pointer.value = { x: e.clientX, y: e.clientY }
|
|
335
|
+
nextTick(() => updateOver(e.clientY))
|
|
336
|
+
window.addEventListener('pointermove', onPointerMove)
|
|
337
|
+
window.addEventListener('pointerup', onPointerUp)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function onPointerMove(e: PointerEvent) {
|
|
341
|
+
if (!dragActive.value) return
|
|
342
|
+
pointer.value = { x: e.clientX, y: e.clientY }
|
|
343
|
+
offsetLeft.value = e.clientX - startX
|
|
344
|
+
updateOver(e.clientY)
|
|
316
345
|
}
|
|
317
346
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
347
|
+
function onPointerUp() {
|
|
348
|
+
window.removeEventListener('pointermove', onPointerMove)
|
|
349
|
+
window.removeEventListener('pointerup', onPointerUp)
|
|
350
|
+
const p = projected.value
|
|
351
|
+
const id = activeItemId.value
|
|
352
|
+
dragActive.value = false
|
|
353
|
+
if (!p || id === undefined) return
|
|
354
|
+
|
|
355
|
+
// Index among the target parent's children
|
|
356
|
+
const items = displayItems.value
|
|
357
|
+
const upto = items.slice(0, p.overIndex + 1)
|
|
358
|
+
const prevSibling = [...upto].reverse().find(i => i.parentId === p.parentId && i.depth === p.depth)
|
|
359
|
+
const targetChildren = (p.parentId === null || p.parentId === rootParentId.value)
|
|
360
|
+
? tree.value
|
|
361
|
+
: (findNode(tree.value, p.parentId)?.children ?? [])
|
|
362
|
+
const newIndex = prevSibling
|
|
363
|
+
? targetChildren.findIndex(c => c.id === prevSibling.id) + 1
|
|
364
|
+
: 0
|
|
365
|
+
|
|
366
|
+
moveItem(id, p.parentId, newIndex)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
onBeforeUnmount(() => {
|
|
370
|
+
window.removeEventListener('pointermove', onPointerMove)
|
|
371
|
+
window.removeEventListener('pointerup', onPointerUp)
|
|
329
372
|
})
|
|
330
373
|
</script>
|
|
331
374
|
|
|
@@ -343,13 +386,126 @@ provide('categoryTree', {
|
|
|
343
386
|
</div>
|
|
344
387
|
<div
|
|
345
388
|
v-else
|
|
346
|
-
|
|
389
|
+
ref="containerRef"
|
|
390
|
+
class="relative text-sm select-none"
|
|
391
|
+
:class="dragActive ? 'cursor-grabbing' : ''"
|
|
347
392
|
>
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
393
|
+
<!-- Insertion line at the very top -->
|
|
394
|
+
<div
|
|
395
|
+
v-if="dragActive && projected && projected.overItemId === null"
|
|
396
|
+
class="pointer-events-none py-0.5"
|
|
397
|
+
:style="{ paddingLeft: (projected.depth * INDENT + 8) + 'px' }"
|
|
398
|
+
>
|
|
399
|
+
<span class="inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 border-2 border-dashed border-[var(--ui-primary)] bg-[var(--ui-primary)]/5 text-[var(--ui-primary)]/70 text-sm">
|
|
400
|
+
<UIcon
|
|
401
|
+
name="i-lucide-grip-vertical"
|
|
402
|
+
class="size-4 opacity-40"
|
|
403
|
+
/>
|
|
404
|
+
{{ activeItem?.name || '…' }}
|
|
405
|
+
</span>
|
|
406
|
+
</div>
|
|
407
|
+
|
|
408
|
+
<template
|
|
409
|
+
v-for="item in displayItems"
|
|
410
|
+
:key="item.id"
|
|
411
|
+
>
|
|
412
|
+
<!-- Active (current) item rendered in place as a grabbable pill -->
|
|
413
|
+
<div
|
|
414
|
+
v-if="item.id === activeItemId"
|
|
415
|
+
:data-flat-id="item.id"
|
|
416
|
+
class="flex items-center gap-1 px-2 py-1.5 my-0.5"
|
|
417
|
+
:style="{ paddingLeft: (item.depth * INDENT + 8) + 'px' }"
|
|
418
|
+
>
|
|
419
|
+
<span class="shrink-0 size-5 sm:size-4" />
|
|
420
|
+
<span
|
|
421
|
+
class="inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 -my-0.5 ring-2 ring-[var(--ui-primary)] bg-[var(--ui-primary)]/10 font-semibold text-[var(--ui-primary)] cursor-grab active:cursor-grabbing touch-none"
|
|
422
|
+
@pointerdown="onHandlePointerDown"
|
|
423
|
+
>
|
|
424
|
+
<UIcon
|
|
425
|
+
name="i-lucide-grip-vertical"
|
|
426
|
+
class="shrink-0 size-4 opacity-60"
|
|
427
|
+
/>
|
|
428
|
+
{{ item.name || '…' }}
|
|
429
|
+
</span>
|
|
430
|
+
</div>
|
|
431
|
+
<div
|
|
432
|
+
v-else
|
|
433
|
+
:data-flat-id="item.id"
|
|
434
|
+
class="flex items-center gap-1 px-2 py-1.5 my-0.5 rounded-md transition-colors"
|
|
435
|
+
:class="[
|
|
436
|
+
nonSelectableIds.has(item.id) ? 'opacity-40' : '',
|
|
437
|
+
dragActive && projected && projected.parentId === item.id ? 'ring-2 ring-[var(--ui-primary)] bg-[var(--ui-primary)]/10' : ''
|
|
438
|
+
]"
|
|
439
|
+
:style="{ paddingLeft: (item.depth * INDENT + 8) + 'px' }"
|
|
440
|
+
>
|
|
441
|
+
<button
|
|
442
|
+
v-if="item.hasChildren"
|
|
443
|
+
class="shrink-0 size-5 sm:size-4 flex items-center justify-center rounded hover:bg-[var(--ui-bg-elevated)] transition-colors cursor-pointer"
|
|
444
|
+
type="button"
|
|
445
|
+
@click.stop="toggleNode(item.id)"
|
|
446
|
+
>
|
|
447
|
+
<UIcon
|
|
448
|
+
name="i-lucide-chevron-right"
|
|
449
|
+
class="size-3.5 transition-transform duration-200"
|
|
450
|
+
:class="{ 'rotate-90': isExpanded(item.id) }"
|
|
451
|
+
/>
|
|
452
|
+
</button>
|
|
453
|
+
<span
|
|
454
|
+
v-else
|
|
455
|
+
class="shrink-0 size-5 sm:size-4"
|
|
456
|
+
/>
|
|
457
|
+
<UIcon
|
|
458
|
+
:name="item.hasChildren
|
|
459
|
+
? ((isExpanded(item.id) || (dragActive && projected && projected.parentId === item.id)) ? 'i-lucide-folder-open' : 'i-lucide-folder')
|
|
460
|
+
: 'i-lucide-file'"
|
|
461
|
+
class="shrink-0 size-4"
|
|
462
|
+
:class="dragActive && projected && projected.parentId === item.id ? 'text-[var(--ui-primary)]' : 'opacity-60'"
|
|
463
|
+
/>
|
|
464
|
+
<span class="truncate">{{ item.name }}</span>
|
|
465
|
+
</div>
|
|
466
|
+
|
|
467
|
+
<!-- Insertion line after this row -->
|
|
468
|
+
<div
|
|
469
|
+
v-if="dragActive && projected && projected.overItemId === item.id"
|
|
470
|
+
class="pointer-events-none py-0.5"
|
|
471
|
+
:style="{ paddingLeft: (projected.depth * INDENT + 8) + 'px' }"
|
|
472
|
+
>
|
|
473
|
+
<span class="inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 border-2 border-dashed border-[var(--ui-primary)] bg-[var(--ui-primary)]/5 text-[var(--ui-primary)]/70 text-sm">
|
|
474
|
+
<UIcon
|
|
475
|
+
name="i-lucide-grip-vertical"
|
|
476
|
+
class="size-4 opacity-40"
|
|
477
|
+
/>
|
|
478
|
+
{{ activeItem?.name || '…' }}
|
|
479
|
+
</span>
|
|
480
|
+
</div>
|
|
481
|
+
</template>
|
|
482
|
+
|
|
483
|
+
<!-- Floating chip + level hint following the cursor while dragging -->
|
|
484
|
+
<Teleport to="body">
|
|
485
|
+
<div
|
|
486
|
+
v-if="dragActive && activeItem"
|
|
487
|
+
class="fixed z-50 pointer-events-none -translate-y-1/2"
|
|
488
|
+
:style="{ left: pointer.x + 14 + 'px', top: pointer.y + 'px' }"
|
|
489
|
+
>
|
|
490
|
+
<span class="inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 ring-2 ring-[var(--ui-primary)] bg-[var(--ui-bg-default,#fff)] shadow-lg font-semibold text-[var(--ui-primary)] text-sm">
|
|
491
|
+
<UIcon
|
|
492
|
+
name="i-lucide-grip-vertical"
|
|
493
|
+
class="shrink-0 size-4 opacity-60"
|
|
494
|
+
/>
|
|
495
|
+
{{ activeItem.name || '…' }}
|
|
496
|
+
</span>
|
|
497
|
+
<span
|
|
498
|
+
v-if="projected"
|
|
499
|
+
class="mt-1 flex items-center gap-1 w-fit rounded px-1.5 py-0.5 bg-[var(--ui-primary)] text-white text-[11px] shadow"
|
|
500
|
+
>
|
|
501
|
+
<UIcon
|
|
502
|
+
name="i-lucide-corner-down-right"
|
|
503
|
+
class="size-3"
|
|
504
|
+
/>
|
|
505
|
+
{{ projectedParentName || '—' }}
|
|
506
|
+
</span>
|
|
507
|
+
</div>
|
|
508
|
+
</Teleport>
|
|
353
509
|
</div>
|
|
354
510
|
</ClientOnly>
|
|
355
511
|
</template>
|