@meistrari/tela-build 1.29.2 → 1.30.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/components/tela/button/button.vue +2 -2
- package/components/tela/combobox/combobox-item.vue +1 -1
- package/components/tela/combobox/combobox-list.vue +1 -1
- package/components/tela/combobox/combobox.vue +206 -64
- package/components/tela/preview/preview-content.vue +46 -2
- package/package.json +1 -1
|
@@ -41,8 +41,8 @@ const variantStyle = computed(() => (({
|
|
|
41
41
|
'secondary': fold`
|
|
42
42
|
bg-white text-gray-900 border border-0.5px
|
|
43
43
|
[box-shadow:0_1px_6px_0_rgba(103,127,148,0.05)]
|
|
44
|
-
hover:bg-
|
|
45
|
-
active:bg-
|
|
44
|
+
hover:bg-subtle hover:border-gray-300
|
|
45
|
+
active:bg-muted active:border-gray-400/60
|
|
46
46
|
focus-visible:ring-0.5px focus-visible:ring-cyan-600
|
|
47
47
|
`,
|
|
48
48
|
'ghost': fold`
|
|
@@ -21,7 +21,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|
|
21
21
|
<template>
|
|
22
22
|
<ComboboxItem
|
|
23
23
|
v-bind="forwarded"
|
|
24
|
-
:class="cn('relative flex cursor-pointer gap-2 select-none justify-between items-center rounded-lg px-2 py-1.5 text-sm font-medium outline-none data-[highlighted]:bg-
|
|
24
|
+
:class="cn('relative flex cursor-pointer gap-2 select-none justify-between items-center rounded-lg px-2 py-1.5 text-sm font-medium outline-none data-[highlighted]:bg-muted data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0', props.class)"
|
|
25
25
|
>
|
|
26
26
|
<slot />
|
|
27
27
|
</ComboboxItem>
|
|
@@ -41,7 +41,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|
|
41
41
|
<ComboboxPortal v-else>
|
|
42
42
|
<ComboboxContent
|
|
43
43
|
v-bind="forwarded"
|
|
44
|
-
:class="cn('ComboboxContent z-
|
|
44
|
+
:class="cn('ComboboxContent z-[9999] shadow-lg outline-none rounded-xl bg-white-1000 border-[0.5px] border-gray-200 pointer-events-auto', props.class)"
|
|
45
45
|
>
|
|
46
46
|
<ComboboxViewport>
|
|
47
47
|
<slot />
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref, computed } from 'vue'
|
|
3
|
-
import type { Component, Ref } from 'vue'
|
|
3
|
+
import type { Component, ComponentPublicInstance, Ref } from 'vue'
|
|
4
4
|
import { useMagicKeys } from '@vueuse/core'
|
|
5
5
|
|
|
6
6
|
import ComboboxRoot from './combobox-root.vue'
|
|
@@ -29,6 +29,8 @@ interface ComboboxOption {
|
|
|
29
29
|
isMultiModal?: boolean
|
|
30
30
|
features?: Array<{ text: string }>
|
|
31
31
|
cost?: number
|
|
32
|
+
children?: ComboboxOption[]
|
|
33
|
+
triggerLabel?: string
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
type ComboboxOptions = ComboboxOption[]
|
|
@@ -111,9 +113,21 @@ const currentOption = computed(() => {
|
|
|
111
113
|
return { label: props.placeholder, value: '' }
|
|
112
114
|
}
|
|
113
115
|
|
|
114
|
-
const
|
|
116
|
+
const rawInner = innerValue.value
|
|
117
|
+
const value = (rawInner && typeof rawInner === 'object' ? (rawInner as any).value : rawInner) || props.modelValue
|
|
118
|
+
const found = props.options.find(option => option.value === value)
|
|
119
|
+
if (found)
|
|
120
|
+
return found
|
|
121
|
+
|
|
122
|
+
for (const option of props.options) {
|
|
123
|
+
if (option.children) {
|
|
124
|
+
const child = option.children.find(c => c.value === value)
|
|
125
|
+
if (child)
|
|
126
|
+
return child
|
|
127
|
+
}
|
|
128
|
+
}
|
|
115
129
|
|
|
116
|
-
return
|
|
130
|
+
return props.options[0] ?? { label: props.placeholder || props.labelSelect, value: '' }
|
|
117
131
|
})
|
|
118
132
|
|
|
119
133
|
const tabs = computed(() => {
|
|
@@ -219,23 +233,89 @@ function renderCostIndicator(cost: number | undefined) {
|
|
|
219
233
|
return { blackSymbols, graySymbols }
|
|
220
234
|
}
|
|
221
235
|
|
|
236
|
+
const expandedItem = ref<string | null>(null)
|
|
237
|
+
const submenuStyle = ref<Record<string, string>>({})
|
|
238
|
+
const nestedItemRefs = ref<Record<string, HTMLElement>>({})
|
|
239
|
+
|
|
240
|
+
function setNestedItemRef(value: string, el: Element | ComponentPublicInstance | null) {
|
|
241
|
+
if (!el)
|
|
242
|
+
return
|
|
243
|
+
const htmlEl = '$el' in el ? el.$el as HTMLElement : el as HTMLElement
|
|
244
|
+
nestedItemRefs.value[value] = htmlEl
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function updateSubmenuPosition(itemValue: string) {
|
|
248
|
+
const el = nestedItemRefs.value[itemValue]
|
|
249
|
+
if (!el)
|
|
250
|
+
return
|
|
251
|
+
|
|
252
|
+
const rect = el.getBoundingClientRect()
|
|
253
|
+
const item = props.options.find(o => o.value === itemValue)
|
|
254
|
+
const childCount = item?.children?.length ?? 0
|
|
255
|
+
const estimatedHeight = childCount * 52 + 8
|
|
256
|
+
const viewportHeight = window.innerHeight
|
|
257
|
+
let top = rect.top
|
|
258
|
+
if (top + estimatedHeight > viewportHeight - 8) {
|
|
259
|
+
top = Math.max(8, viewportHeight - estimatedHeight - 8)
|
|
260
|
+
}
|
|
261
|
+
submenuStyle.value = {
|
|
262
|
+
position: 'fixed',
|
|
263
|
+
left: `${rect.right + 2}px`,
|
|
264
|
+
top: `${top}px`,
|
|
265
|
+
zIndex: '9999',
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
222
269
|
function onOpenChange(open: boolean) {
|
|
223
270
|
isOpen.value = open
|
|
271
|
+
if (!open) {
|
|
272
|
+
expandedItem.value = null
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function handleItemMouseEnter(item: ComboboxOption) {
|
|
277
|
+
if (item.children?.length) {
|
|
278
|
+
expandedItem.value = item.value
|
|
279
|
+
updateSubmenuPosition(item.value)
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
expandedItem.value = null
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function handleChildSelect(child: ComboboxOption, event: Event) {
|
|
287
|
+
event.preventDefault()
|
|
288
|
+
event.stopPropagation()
|
|
289
|
+
expandedItem.value = null
|
|
290
|
+
isOpen.value = false
|
|
291
|
+
emit('select', child.value)
|
|
292
|
+
emit('update:modelValue', child.value)
|
|
224
293
|
}
|
|
225
294
|
|
|
226
295
|
function handleSelect(option: any) {
|
|
227
|
-
|
|
228
|
-
|
|
296
|
+
const value = typeof option === 'string' ? option : option?.value
|
|
297
|
+
const found = props.options.find(o => o.value === value)
|
|
298
|
+
if (found?.children?.length)
|
|
299
|
+
return
|
|
300
|
+
emit('select', value)
|
|
301
|
+
emit('update:modelValue', value)
|
|
229
302
|
}
|
|
230
303
|
|
|
231
|
-
watch(innerValue, (
|
|
304
|
+
watch(innerValue, (raw) => {
|
|
305
|
+
if (!raw)
|
|
306
|
+
return
|
|
307
|
+
|
|
308
|
+
const value = typeof raw === 'string' ? raw : (raw as any)?.value
|
|
232
309
|
if (!value)
|
|
233
310
|
return
|
|
234
311
|
|
|
235
|
-
const option = props.options.find(
|
|
312
|
+
const option = props.options.find(o => o.value === value)
|
|
236
313
|
if (!option)
|
|
237
314
|
return
|
|
238
315
|
|
|
316
|
+
if (option.children?.length)
|
|
317
|
+
return
|
|
318
|
+
|
|
239
319
|
innerValue.value = ''
|
|
240
320
|
|
|
241
321
|
emit('select', value)
|
|
@@ -253,7 +333,8 @@ watch(innerValue, (value) => {
|
|
|
253
333
|
as="button"
|
|
254
334
|
tabindex="0"
|
|
255
335
|
:class="cn(
|
|
256
|
-
'group select-none flex items-center justify-between gap-3 rounded-10px px-8px py-7px bg-white border-
|
|
336
|
+
'group select-none flex items-center justify-between gap-3 rounded-10px px-8px py-7px bg-white border-0.5px border transition hover:border-strong hover:bg-subtle data-[state=open]:border-strong data-[state=open]:bg-subtle',
|
|
337
|
+
'[box-shadow:0_1px_6px_0_rgba(103,127,148,0.05)]',
|
|
257
338
|
!compact && 'w-full !py-2.5 pl-3 pr-3',
|
|
258
339
|
props.triggerClass,
|
|
259
340
|
)"
|
|
@@ -273,16 +354,16 @@ watch(innerValue, (value) => {
|
|
|
273
354
|
</div>
|
|
274
355
|
</div>
|
|
275
356
|
<span
|
|
276
|
-
:class="cn('truncate
|
|
357
|
+
:class="cn('truncate text-primary group-data-[state=open]:text-secondary',
|
|
277
358
|
compact ? 'text-sm font-580' : 'text-body-14-regular', props.labelClass)"
|
|
278
359
|
>
|
|
279
|
-
{{ currentOption?.label }}
|
|
360
|
+
{{ currentOption?.triggerLabel || currentOption?.label }}
|
|
280
361
|
</span>
|
|
281
362
|
</div>
|
|
282
|
-
<TelaIcon name="i-ph-caret-down" :class="cn('
|
|
363
|
+
<TelaIcon name="i-ph-caret-down-bold" color="icon-secondary" :class="cn('transition-all ease-out duration-150', isOpen && 'rotate-180')" size="15px" />
|
|
283
364
|
</ComboboxTrigger>
|
|
284
365
|
</ComboboxAnchor>
|
|
285
|
-
<ComboboxList :class="cn('z-999', compact ? 'w-440px' : 'w-327px
|
|
366
|
+
<ComboboxList :class="cn('z-999', compact ? 'w-440px' : 'w-327px', props.contentClass)" :disable-portal="props.disablePortal" align="start">
|
|
286
367
|
<div v-if="hasSearchbar" class="relative">
|
|
287
368
|
<span class="absolute inset-y-0 start-0 flex items-center justify-center px-2.5">
|
|
288
369
|
<TelaIcon name="i-ph-magnifying-glass" :size="compact ? '12px' : '14px'" class="text-gray-400" />
|
|
@@ -440,65 +521,113 @@ watch(innerValue, (value) => {
|
|
|
440
521
|
<ComboboxLabel v-if="!search.trim() && hasGroupingLabels">
|
|
441
522
|
{{ group.heading }}
|
|
442
523
|
</ComboboxLabel>
|
|
443
|
-
<
|
|
444
|
-
<
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
524
|
+
<template v-for="item in group.children" :key="item.value">
|
|
525
|
+
<ComboboxItem
|
|
526
|
+
:ref="(el) => item.children?.length && setNestedItemRef(item.value, el)"
|
|
527
|
+
:value="item"
|
|
528
|
+
:class="cn('group/item py-2', !compact && '!px-1.5')"
|
|
529
|
+
@mouseenter="handleItemMouseEnter(item)"
|
|
530
|
+
@select="(e) => item.children?.length && e.preventDefault()"
|
|
531
|
+
>
|
|
532
|
+
<div class="flex items-center">
|
|
533
|
+
<div :class="cn('flex items-center w-250px', compact ? 'gap-2' : 'gap-1.5')">
|
|
534
|
+
<div v-if="item.icon" class="w-5 h-5 shrink-0 flex items-center justify-center">
|
|
535
|
+
<TelaIcon
|
|
536
|
+
v-if="typeof item.icon === 'string'" :name="item.icon" :color="colorIcon" :size="iconWithBackground?.size"
|
|
537
|
+
:background-class="iconWithBackground?.backgroundClass"
|
|
538
|
+
/>
|
|
539
|
+
<Component
|
|
540
|
+
:is="item.icon"
|
|
541
|
+
v-else
|
|
542
|
+
:class="cn((item?.label.startsWith('DeepSeek') || item?.label.startsWith('Gemini')) && 'scale-75')"
|
|
543
|
+
/>
|
|
544
|
+
</div>
|
|
545
|
+
<div v-if="!item.icon && item?.externalIconSrc">
|
|
546
|
+
<img :src="item.externalIconSrc" :alt="item.label" class="w-5 h-5">
|
|
547
|
+
</div>
|
|
548
|
+
<div class="flex flex-col">
|
|
549
|
+
<div class="flex items-center gap-1">
|
|
550
|
+
<span :class="cn('font-medium truncate max-w-140px', compact ? 'text-sm font-580' : 'text-body-14-regular', labelItemClass)">{{ item.label }}</span>
|
|
551
|
+
<TelaBadge v-if="item.isMultiModal" variant="filled" class="py-[2px] bg-gray-100" text-class="leading-none">
|
|
552
|
+
{{ labelMultimodal }}
|
|
553
|
+
</TelaBadge>
|
|
554
|
+
<span v-if="item.cost !== undefined" class="text-10px font-580 ml-1px">
|
|
555
|
+
<span class="text-black-900">{{ renderCostIndicator(item.cost)?.blackSymbols }}</span>
|
|
556
|
+
<span class="text-gray-300">{{ renderCostIndicator(item.cost)?.graySymbols }}</span>
|
|
557
|
+
</span>
|
|
558
|
+
</div>
|
|
559
|
+
<span v-if="item.description" :class="cn('font-normal text-sm leading-none text-gray-500', descriptionClass)">
|
|
560
|
+
{{ item.description }}
|
|
561
|
+
</span>
|
|
562
|
+
<slot name="tags" :option="item" />
|
|
563
|
+
</div>
|
|
459
564
|
</div>
|
|
460
|
-
<div class="flex
|
|
461
|
-
<div class="flex
|
|
462
|
-
<span
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
565
|
+
<div v-if="item.maxInputTokens || item.maxOutputTokens" class="flex gap-3">
|
|
566
|
+
<div class="flex flex-col gap-1.5">
|
|
567
|
+
<span class="text-gray-400 leading-none text-9px uppercase tracking-wider font-semibold">
|
|
568
|
+
{{ labelInputMax }}
|
|
569
|
+
</span>
|
|
570
|
+
<span class="text-gray-400 leading-none text-11px uppercase font-semibold group-data-[highlighted]:text-gray-700">
|
|
571
|
+
{{ handleFormatNumber(item.maxInputTokens) }}
|
|
572
|
+
</span>
|
|
573
|
+
</div>
|
|
574
|
+
<div class="flex flex-col gap-1.5">
|
|
575
|
+
<span class="text-gray-400 leading-none text-9px uppercase tracking-wider font-semibold">
|
|
576
|
+
{{ labelOutputMax }}
|
|
577
|
+
</span>
|
|
578
|
+
<span class="text-gray-400 leading-none text-11px uppercase font-semibold group-data-[highlighted]:text-gray-700">
|
|
579
|
+
{{ handleFormatNumber(item.maxOutputTokens) }}
|
|
469
580
|
</span>
|
|
470
581
|
</div>
|
|
471
|
-
<span v-if="item.description" :class="cn('font-normal text-sm leading-none text-gray-500', descriptionClass)">
|
|
472
|
-
{{ item.description }}
|
|
473
|
-
</span>
|
|
474
|
-
<slot name="tags" :option="item" />
|
|
475
582
|
</div>
|
|
476
583
|
</div>
|
|
477
|
-
<div
|
|
478
|
-
<
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
584
|
+
<div class="flex gap-2 items-center">
|
|
585
|
+
<TelaIcon v-if="item.children?.length" name="i-ph-caret-right" size="14px" class="text-gray-400" />
|
|
586
|
+
<ComboboxItemIndicator v-else>
|
|
587
|
+
<TelaIcon name="i-ph-check" size="14px" />
|
|
588
|
+
</ComboboxItemIndicator>
|
|
589
|
+
</div>
|
|
590
|
+
</ComboboxItem>
|
|
591
|
+
<!-- Nested submenu -->
|
|
592
|
+
<Teleport v-if="item.children?.length" to="body">
|
|
593
|
+
<div
|
|
594
|
+
v-if="expandedItem === item.value"
|
|
595
|
+
:style="submenuStyle"
|
|
596
|
+
class="min-w-200px bg-white border border-strong rounded-12px shadow-[0_10px_15px_-3px_rgba(0,0,0,0.1),0_4px_6px_-4px_rgba(0,0,0,0.1)] pointer-events-auto"
|
|
597
|
+
@mouseenter="expandedItem = item.value"
|
|
598
|
+
@pointerdown.stop
|
|
599
|
+
>
|
|
600
|
+
<div p-4px>
|
|
601
|
+
<div
|
|
602
|
+
v-for="child in item.children"
|
|
603
|
+
:key="child.value"
|
|
604
|
+
:class="cn(
|
|
605
|
+
'relative flex cursor-pointer select-none justify-between items-center rounded-lg px-2 py-2 text-sm font-medium text-gray-900 outline-none hover:bg-gray-100',
|
|
606
|
+
!compact && '!px-1.5',
|
|
607
|
+
)"
|
|
608
|
+
@click.stop="handleChildSelect(child, $event)"
|
|
609
|
+
>
|
|
610
|
+
<div :class="cn('flex items-center', compact ? 'gap-2' : 'gap-1.5')">
|
|
611
|
+
<div v-if="child.icon" class="w-5 h-5 shrink-0 flex items-center justify-center">
|
|
612
|
+
<TelaIcon
|
|
613
|
+
v-if="typeof child.icon === 'string'" :name="child.icon" :color="colorIcon" :size="iconWithBackground?.size"
|
|
614
|
+
:background-class="iconWithBackground?.backgroundClass"
|
|
615
|
+
/>
|
|
616
|
+
<Component :is="child.icon" v-else />
|
|
617
|
+
</div>
|
|
618
|
+
<div class="flex flex-col">
|
|
619
|
+
<span :class="cn('font-medium truncate max-w-140px', compact ? 'text-sm font-580' : 'text-body-14-regular', labelItemClass)">{{ child.label }}</span>
|
|
620
|
+
<span v-if="child.description" :class="cn('font-normal text-sm leading-none text-gray-500', descriptionClass)">
|
|
621
|
+
{{ child.description }}
|
|
622
|
+
</span>
|
|
623
|
+
</div>
|
|
624
|
+
</div>
|
|
625
|
+
<TelaIcon v-if="child.value === (modelValue || innerValue)" name="i-ph-check" size="14px" />
|
|
626
|
+
</div>
|
|
493
627
|
</div>
|
|
494
628
|
</div>
|
|
495
|
-
</
|
|
496
|
-
|
|
497
|
-
<ComboboxItemIndicator>
|
|
498
|
-
<TelaIcon name="i-ph-check" size="14px" />
|
|
499
|
-
</ComboboxItemIndicator>
|
|
500
|
-
</div>
|
|
501
|
-
</ComboboxItem>
|
|
629
|
+
</Teleport>
|
|
630
|
+
</template>
|
|
502
631
|
</ComboboxGroup>
|
|
503
632
|
</template>
|
|
504
633
|
</div>
|
|
@@ -521,3 +650,16 @@ watch(innerValue, (value) => {
|
|
|
521
650
|
}
|
|
522
651
|
}
|
|
523
652
|
</style>
|
|
653
|
+
|
|
654
|
+
<style>
|
|
655
|
+
@keyframes combobox-submenuFadeIn {
|
|
656
|
+
from {
|
|
657
|
+
opacity: 0;
|
|
658
|
+
transform: translateX(-4px);
|
|
659
|
+
}
|
|
660
|
+
to {
|
|
661
|
+
opacity: 1;
|
|
662
|
+
transform: translateX(0);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
</style>
|
|
@@ -786,11 +786,55 @@ function scrollToPage(page: number) {
|
|
|
786
786
|
}
|
|
787
787
|
}
|
|
788
788
|
|
|
789
|
-
function download(url: string) {
|
|
789
|
+
async function download(url: string) {
|
|
790
790
|
if (!url)
|
|
791
791
|
return
|
|
792
792
|
|
|
793
|
-
|
|
793
|
+
const filename = props.file.fileName || 'download'
|
|
794
|
+
|
|
795
|
+
function triggerAnchor(href: string, revokeAfter?: string) {
|
|
796
|
+
const a = document.createElement('a')
|
|
797
|
+
a.href = href
|
|
798
|
+
a.download = filename
|
|
799
|
+
document.body.appendChild(a)
|
|
800
|
+
a.click()
|
|
801
|
+
document.body.removeChild(a)
|
|
802
|
+
if (revokeAfter) {
|
|
803
|
+
setTimeout(() => URL.revokeObjectURL(revokeAfter), 10_000)
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
if (url.startsWith('data:')) {
|
|
808
|
+
try {
|
|
809
|
+
const response = await fetch(url)
|
|
810
|
+
const blob = await response.blob()
|
|
811
|
+
const blobUrl = URL.createObjectURL(blob)
|
|
812
|
+
triggerAnchor(blobUrl, blobUrl)
|
|
813
|
+
}
|
|
814
|
+
catch {
|
|
815
|
+
triggerAnchor(url)
|
|
816
|
+
}
|
|
817
|
+
return
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
try {
|
|
821
|
+
const response = await fetch(url)
|
|
822
|
+
if (!response.ok)
|
|
823
|
+
throw new Error(`HTTP ${response.status}`)
|
|
824
|
+
const blob = await response.blob()
|
|
825
|
+
const blobUrl = URL.createObjectURL(blob)
|
|
826
|
+
triggerAnchor(blobUrl, blobUrl)
|
|
827
|
+
}
|
|
828
|
+
catch {
|
|
829
|
+
const a = document.createElement('a')
|
|
830
|
+
a.href = url
|
|
831
|
+
a.download = filename
|
|
832
|
+
a.target = '_blank'
|
|
833
|
+
a.rel = 'noopener noreferrer'
|
|
834
|
+
document.body.appendChild(a)
|
|
835
|
+
a.click()
|
|
836
|
+
document.body.removeChild(a)
|
|
837
|
+
}
|
|
794
838
|
}
|
|
795
839
|
|
|
796
840
|
const isContainerHovered = ref(false)
|