nuxt-ui-tools 1.25.0 → 1.25.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.
Files changed (40) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/display/components/blocks/display-accordion.vue +4 -1
  3. package/dist/runtime/display/components/blocks/display-alert.vue +5 -2
  4. package/dist/runtime/display/components/blocks/display-breakdown.vue +13 -7
  5. package/dist/runtime/display/components/blocks/display-chart.vue +6 -2
  6. package/dist/runtime/display/components/blocks/display-code.vue +2 -1
  7. package/dist/runtime/display/components/blocks/display-date.vue +21 -16
  8. package/dist/runtime/display/components/blocks/display-diff.vue +19 -7
  9. package/dist/runtime/display/components/blocks/display-empty.vue +3 -2
  10. package/dist/runtime/display/components/blocks/display-facts.vue +24 -9
  11. package/dist/runtime/display/components/blocks/display-heading.vue +10 -3
  12. package/dist/runtime/display/components/blocks/display-list.vue +10 -4
  13. package/dist/runtime/display/components/blocks/display-media.vue +19 -9
  14. package/dist/runtime/display/components/blocks/display-meter.vue +12 -11
  15. package/dist/runtime/display/components/blocks/display-person.vue +12 -5
  16. package/dist/runtime/display/components/blocks/display-record.vue +78 -18
  17. package/dist/runtime/display/components/blocks/display-stat.vue +16 -5
  18. package/dist/runtime/display/components/blocks/display-table.vue +4 -0
  19. package/dist/runtime/display/components/blocks/display-text.vue +8 -2
  20. package/dist/runtime/display/components/blocks/display-timeline.vue +7 -1
  21. package/dist/runtime/display/components/parts/display-actions.vue +4 -1
  22. package/dist/runtime/display/components/parts/display-chip.vue +27 -1
  23. package/dist/runtime/display/components/parts/display-fact-value.vue +2 -1
  24. package/dist/runtime/display/components/parts/display-record-media.d.vue.ts +2 -0
  25. package/dist/runtime/display/components/parts/display-record-media.vue +5 -3
  26. package/dist/runtime/display/components/parts/display-record-media.vue.d.ts +2 -0
  27. package/dist/runtime/display/directives/reveal-truncated.d.ts +10 -0
  28. package/dist/runtime/display/directives/reveal-truncated.js +31 -0
  29. package/dist/runtime/display/types/blocks/diff.d.ts +1 -1
  30. package/dist/runtime/display/types/blocks/facts.d.ts +3 -1
  31. package/dist/runtime/display/types/blocks/person.d.ts +3 -1
  32. package/dist/runtime/display/types/blocks/record.d.ts +4 -2
  33. package/dist/runtime/display/types/config.d.ts +9 -2
  34. package/dist/runtime/display/types/shared.d.ts +9 -0
  35. package/dist/runtime/display/utils/config.d.ts +7 -2
  36. package/dist/runtime/display/utils/config.js +11 -1
  37. package/dist/runtime/display/utils/links.d.ts +12 -0
  38. package/dist/runtime/display/utils/links.js +9 -0
  39. package/dist/runtime/form/composables/use-form-root.js +15 -4
  40. package/package.json +1 -1
package/dist/module.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "configKey": "nuxtUiTools",
6
6
  "docs": "https://ui.nuxt.com/docs/getting-started/installation/nuxt",
7
7
  "name": "nuxt-ui-tools",
8
- "version": "1.25.0",
8
+ "version": "1.25.1",
9
9
  "builder": {
10
10
  "@nuxt/module-builder": "1.0.3",
11
11
  "unbuild": "unknown"
@@ -3,6 +3,7 @@ import UIcon from "@nuxt/ui/components/Icon.vue";
3
3
  import { computed, defineAsyncComponent, ref, useId } from "vue";
4
4
  import { useDisplayAppUi } from "../../composables/use-display-config";
5
5
  import { useDisplayIcons } from "../../composables/use-display-icons";
6
+ import { vRevealTruncated } from "../../directives/reveal-truncated";
6
7
  import { resolveDisplayClasses } from "../../utils/classes";
7
8
  import DisplayRender from "../parts/display-render";
8
9
  import DisplaySkeleton from "../parts/display-skeleton.vue";
@@ -67,7 +68,9 @@ function toggle(index) {
67
68
  @click="toggle(index)"
68
69
  >
69
70
  <UIcon v-if="item.icon" :name="item.icon" :class="classes.icon" />
70
- <span :class="classes.label"><DisplayRender :content="item.label" /></span>
71
+ <span v-reveal-truncated :class="classes.label"
72
+ ><DisplayRender :content="item.label"
73
+ /></span>
71
74
  <UIcon
72
75
  :name="icons.chevronDown"
73
76
  :class="[classes.chevron, open.includes(index) ? 'rotate-180' : '']"
@@ -4,6 +4,7 @@ import { computed, ref } from "vue";
4
4
  import { useDisplayAppUi } from "../../composables/use-display-config";
5
5
  import { toneIcon, useDisplayIcons } from "../../composables/use-display-icons";
6
6
  import { useDisplayLocale } from "../../composables/use-display-locale";
7
+ import { vRevealTruncated } from "../../directives/reveal-truncated";
7
8
  import { resolveDisplayClasses } from "../../utils/classes";
8
9
  import { isDisplayContentEmpty } from "../../utils/content";
9
10
  import { displayToneClass } from "../../utils/tone";
@@ -93,8 +94,10 @@ const classes = computed(
93
94
  </span>
94
95
  <UIcon v-else-if="resolvedIcon" :name="resolvedIcon" :class="classes.icon" />
95
96
  <div :class="classes.body">
96
- <div v-if="titled" :class="classes.title"><DisplayRender :content="title" /></div>
97
- <div v-if="!isDisplayContentEmpty(message)" :class="classes.message">
97
+ <div v-if="titled" v-reveal-truncated :class="classes.title">
98
+ <DisplayRender :content="title" />
99
+ </div>
100
+ <div v-if="!isDisplayContentEmpty(message)" v-reveal-truncated :class="classes.message">
98
101
  <DisplayRender :content="message" />
99
102
  </div>
100
103
  <div v-if="items?.length" :class="classes.items">
@@ -5,6 +5,7 @@ import { computed, ref } from "vue";
5
5
  import { useDisplayAppUi } from "../../composables/use-display-config";
6
6
  import { useDisplayIcons } from "../../composables/use-display-icons";
7
7
  import { useDisplayLocale } from "../../composables/use-display-locale";
8
+ import { vRevealTruncated } from "../../directives/reveal-truncated";
8
9
  import { resolveDisplayClasses } from "../../utils/classes";
9
10
  import { isDisplayContentEmpty } from "../../utils/content";
10
11
  import { DISPLAY_SURFACE_CLASSES, displayToneClass } from "../../utils/tone";
@@ -100,6 +101,7 @@ function valueClass(line) {
100
101
  <td
101
102
  v-for="(cell, cellIndex) in line.values ?? []"
102
103
  :key="cellIndex"
104
+ v-reveal-truncated
103
105
  :class="['px-4 py-2 text-end', valueClass(line)]"
104
106
  >
105
107
  <DisplayRender :content="cell" />
@@ -122,7 +124,7 @@ function valueClass(line) {
122
124
  </div>
123
125
  <div v-else-if="mode === 'collapsible'" :class="classes.root">
124
126
  <div :class="classes.total">
125
- <span :class="classes.totalLabel">
127
+ <span v-reveal-truncated :class="classes.totalLabel">
126
128
  <DisplayRender :content="total?.label ?? title" />
127
129
  <DisplayChip v-if="total?.chip" :chip="total.chip" />
128
130
  </span>
@@ -140,31 +142,35 @@ function valueClass(line) {
140
142
  </div>
141
143
  <div v-if="open" :class="classes.body">
142
144
  <div v-for="(line, index) in lines" :key="index" :class="lineClass(line)">
143
- <span :class="classes.label">
145
+ <span v-reveal-truncated :class="classes.label">
144
146
  <DisplayRender :content="line.label" />
145
147
  <span v-if="!isDisplayContentEmpty(line.detail)" :class="classes.detail"
146
148
  ><DisplayRender :content="line.detail"
147
149
  /></span>
148
150
  </span>
149
- <span :class="valueClass(line)"><DisplayRender :content="line.value" /></span>
151
+ <span v-reveal-truncated :class="valueClass(line)"
152
+ ><DisplayRender :content="line.value"
153
+ /></span>
150
154
  </div>
151
155
  </div>
152
156
  </div>
153
157
  <div v-else :class="classes.root">
154
- <div v-if="!isDisplayContentEmpty(title)" :class="classes.title">
158
+ <div v-if="!isDisplayContentEmpty(title)" v-reveal-truncated :class="classes.title">
155
159
  <DisplayRender :content="title" />
156
160
  </div>
157
161
  <div v-for="(line, index) in lines" :key="index" :class="lineClass(line)">
158
- <span :class="classes.label">
162
+ <span v-reveal-truncated :class="classes.label">
159
163
  <DisplayRender :content="line.label" />
160
164
  <span v-if="!isDisplayContentEmpty(line.detail)" :class="classes.detail"
161
165
  ><DisplayRender :content="line.detail"
162
166
  /></span>
163
167
  </span>
164
- <span :class="valueClass(line)"><DisplayRender :content="line.value" /></span>
168
+ <span v-reveal-truncated :class="valueClass(line)"
169
+ ><DisplayRender :content="line.value"
170
+ /></span>
165
171
  </div>
166
172
  <div v-if="total" :class="classes.total">
167
- <span :class="classes.totalLabel">
173
+ <span v-reveal-truncated :class="classes.totalLabel">
168
174
  <DisplayRender :content="total.label" />
169
175
  <DisplayChip v-if="total.chip" :chip="total.chip" />
170
176
  </span>
@@ -2,6 +2,7 @@
2
2
  import { computed } from "vue";
3
3
  import { useDisplayAppUi } from "../../composables/use-display-config";
4
4
  import { useDisplayNumber } from "../../composables/use-display-number";
5
+ import { vRevealTruncated } from "../../directives/reveal-truncated";
5
6
  import { resolveDisplayClasses } from "../../utils/classes";
6
7
  import { isDisplayContentEmpty, resolveDisplayText } from "../../utils/content";
7
8
  import DisplayEmptyLine from "../parts/display-empty-line.vue";
@@ -113,7 +114,7 @@ function colorOf(datum, index) {
113
114
  v-if="!isDisplayContentEmpty(title) || !isDisplayContentEmpty(aside)"
114
115
  :class="classes.header"
115
116
  >
116
- <span :class="classes.title"><DisplayRender :content="title" /></span>
117
+ <span v-reveal-truncated :class="classes.title"><DisplayRender :content="title" /></span>
117
118
  <span :class="classes.aside"><DisplayRender :content="aside" /></span>
118
119
  </div>
119
120
  <DisplayEmptyLine v-if="!data.length" :content="empty" :class="classes.plot" />
@@ -145,6 +146,7 @@ function colorOf(datum, index) {
145
146
  <span
146
147
  v-for="(datum, index) in data"
147
148
  :key="index"
149
+ v-reveal-truncated
148
150
  class="min-w-0 flex-1 truncate text-center"
149
151
  >
150
152
  {{ resolveDisplayText(datum.label) }}
@@ -220,7 +222,9 @@ function colorOf(datum, index) {
220
222
  </div>
221
223
  <div v-else-if="variant === 'funnel'" :class="[classes.plot, 'flex flex-col gap-1.5']">
222
224
  <div v-for="(step, index) in steps" :key="index" :class="classes.step">
223
- <span :class="classes.stepLabel">{{ resolveDisplayText(step.label) }}</span>
225
+ <span v-reveal-truncated :class="classes.stepLabel">{{
226
+ resolveDisplayText(step.label)
227
+ }}</span>
224
228
  <span
225
229
  class="h-4 rounded-[3px]"
226
230
  :style="{
@@ -6,6 +6,7 @@ import { isNumber } from "../../../shared/utils/predicate";
6
6
  import { useDisplayAppUi } from "../../composables/use-display-config";
7
7
  import { useDisplayIcons } from "../../composables/use-display-icons";
8
8
  import { useDisplayLocale } from "../../composables/use-display-locale";
9
+ import { vRevealTruncated } from "../../directives/reveal-truncated";
9
10
  import { resolveDisplayClasses } from "../../utils/classes";
10
11
  import { resolveDisplayText } from "../../utils/content";
11
12
  import { tokenizeJson } from "../../utils/json";
@@ -109,7 +110,7 @@ const classes = computed(
109
110
  </div>
110
111
  <div v-else :class="classes.root" data-display="code" :data-variant="variant">
111
112
  <UIcon v-if="icon" :name="icon" :class="classes.icon" />
112
- <span :class="classes.value" data-display-value>{{
113
+ <span v-reveal-truncated :class="classes.value" data-display-value>{{
113
114
  variant === "secret" && !revealed ? masked : value
114
115
  }}</span>
115
116
  <div :class="classes.actions">
@@ -1,10 +1,10 @@
1
1
  <script setup>
2
- import UBadge from "@nuxt/ui/components/Badge.vue";
3
2
  import UIcon from "@nuxt/ui/components/Icon.vue";
4
3
  import { computed } from "vue";
5
4
  import { useDisplayAppUi } from "../../composables/use-display-config";
6
5
  import { useDisplayIcons } from "../../composables/use-display-icons";
7
6
  import { useDisplayLocale } from "../../composables/use-display-locale";
7
+ import { vRevealTruncated } from "../../directives/reveal-truncated";
8
8
  import { resolveDisplayClasses } from "../../utils/classes";
9
9
  import { isDisplayContentEmpty, resolveDisplayText } from "../../utils/content";
10
10
  import {
@@ -16,6 +16,7 @@ import {
16
16
  toDisplayDate
17
17
  } from "../../utils/dates";
18
18
  import { displayToneClass } from "../../utils/tone";
19
+ import DisplayChip from "../parts/display-chip.vue";
19
20
  import DisplayRender from "../parts/display-render";
20
21
  import DisplaySkeleton from "../parts/display-skeleton.vue";
21
22
  const {
@@ -83,7 +84,10 @@ const countdownText = computed(() => {
83
84
  return t("display.overdue", { duration: formatDayCount(-days.value, code.value) });
84
85
  return formatRelativeDays(days.value, code.value);
85
86
  });
86
- const longDate = (date) => date ? formatDisplayDate(date, code.value, time ? LONG_DATE_TIME_FORMAT : LONG_DATE_FORMAT) : "";
87
+ const longDate = (date) => date ? formatDisplayDate(date, code.value, {
88
+ ...time ? LONG_DATE_TIME_FORMAT : LONG_DATE_FORMAT,
89
+ year: date.getFullYear() === reference.value.getFullYear() ? void 0 : "numeric"
90
+ }) : "";
87
91
  const duration = computed(
88
92
  () => start.value && finish.value ? formatDayCount(calendarDaysBetween(start.value, finish.value) + 1, code.value) : void 0
89
93
  );
@@ -124,8 +128,10 @@ const classes = computed(
124
128
  <template v-if="variant === 'countdown'">
125
129
  <UIcon :name="icon ?? (overdue ? icons.warning : 'i-lucide-clock')" :class="classes.icon" />
126
130
  <div :class="classes.body">
127
- <div :class="classes.value" data-display-countdown>{{ countdownText }}</div>
128
- <div :class="classes.caption">
131
+ <div v-reveal-truncated :class="classes.value" data-display-countdown>
132
+ {{ countdownText }}
133
+ </div>
134
+ <div v-reveal-truncated :class="classes.caption">
129
135
  <DisplayRender
130
136
  :content="
131
137
  description ?? (deadline ? t('display.dueOn', { date: longDate(start) }) : longDate(start))
@@ -137,21 +143,14 @@ const classes = computed(
137
143
  <template v-else-if="variant === 'range'">
138
144
  <div :class="classes.bound">
139
145
  <span :class="classes.boundLabel">{{ t("display.from") }}</span>
140
- <span :class="classes.boundValue">{{ longDate(start) }}</span>
146
+ <span v-reveal-truncated :class="classes.boundValue">{{ longDate(start) }}</span>
141
147
  </div>
142
148
  <div :class="classes.line">
143
- <UBadge
144
- v-if="duration"
145
- color="neutral"
146
- variant="soft"
147
- size="sm"
148
- :label="duration"
149
- :class="classes.duration"
150
- />
149
+ <DisplayChip v-if="duration" :chip="{ label: duration }" :class="classes.duration" />
151
150
  </div>
152
151
  <div :class="[classes.bound, 'text-end']">
153
152
  <span :class="classes.boundLabel">{{ t("display.to") }}</span>
154
- <span :class="classes.boundValue">{{ longDate(finish) }}</span>
153
+ <span v-reveal-truncated :class="classes.boundValue">{{ longDate(finish) }}</span>
155
154
  </div>
156
155
  </template>
157
156
  <template v-else>
@@ -161,8 +160,14 @@ const classes = computed(
161
160
  <div :class="classes.weekday">{{ leaf?.weekday }}</div>
162
161
  </div>
163
162
  <div :class="classes.body">
164
- <div :class="classes.title"><DisplayRender :content="title ?? longDate(start)" /></div>
165
- <div v-if="!isDisplayContentEmpty(description) || timeRange" :class="classes.description">
163
+ <div v-reveal-truncated :class="classes.title">
164
+ <DisplayRender :content="title ?? longDate(start)" />
165
+ </div>
166
+ <div
167
+ v-if="!isDisplayContentEmpty(description) || timeRange"
168
+ v-reveal-truncated
169
+ :class="classes.description"
170
+ >
166
171
  <UIcon v-if="timeRange" :name="icon ?? 'i-lucide-clock'" class="size-3 shrink-0" />
167
172
  <span v-if="timeRange">{{ timeRange }}</span>
168
173
  <template v-if="timeRange && !isDisplayContentEmpty(description)"> · </template>
@@ -4,6 +4,7 @@ import { computed } from "vue";
4
4
  import { useDisplayAppUi } from "../../composables/use-display-config";
5
5
  import { useDisplayIcons } from "../../composables/use-display-icons";
6
6
  import { useDisplayLocale } from "../../composables/use-display-locale";
7
+ import { vRevealTruncated } from "../../directives/reveal-truncated";
7
8
  import { resolveDisplayClasses } from "../../utils/classes";
8
9
  import { isDisplayContentEmpty } from "../../utils/content";
9
10
  import DisplayChip from "../parts/display-chip.vue";
@@ -42,6 +43,7 @@ const classes = computed(
42
43
  arrow: "size-3.5 shrink-0 text-dimmed",
43
44
  before: "rounded-[4px] bg-[var(--nut-display-error-soft)] px-1.5 py-px text-[var(--nut-display-error-ink)] line-through decoration-[color-mix(in_oklab,currentColor_45%,transparent)]",
44
45
  cell: "border-t border-muted px-3 py-2 align-middle",
46
+ frame: "overflow-hidden rounded-[var(--nut-display-radius)] border border-default",
45
47
  headCell: "bg-elevated px-3 py-2 text-start text-[11px] font-bold tracking-[0.07em] text-muted uppercase",
46
48
  item: "",
47
49
  label: "mb-1 flex items-center gap-2 text-xs text-muted",
@@ -65,12 +67,12 @@ const classes = computed(
65
67
  :count="Math.max(items.length, 2)"
66
68
  />
67
69
  <div v-else :class="classes.root" data-display="diff" :data-variant="variant">
68
- <div v-if="!isDisplayContentEmpty(title)" :class="classes.title">
70
+ <div v-if="!isDisplayContentEmpty(title)" v-reveal-truncated :class="classes.title">
69
71
  <DisplayRender :content="title" />
70
72
  </div>
71
73
  <template v-if="variant === 'inline'">
72
74
  <div v-for="(item, index) in inlineItems" :key="index" :class="classes.item">
73
- <div :class="classes.label">
75
+ <div v-reveal-truncated :class="classes.label">
74
76
  <DisplayRender :content="item.label" />
75
77
  <DisplayChip v-if="item.note" :chip="item.note" :class="classes.note" />
76
78
  </div>
@@ -82,7 +84,10 @@ const classes = computed(
82
84
  <DisplayRender :content="isDisplayContentEmpty(item.after) ? item.before : item.after" />
83
85
  </div>
84
86
  <div v-else :class="classes.values">
85
- <span v-if="!isDisplayContentEmpty(item.before)" :class="classes.before"
87
+ <span
88
+ v-if="!isDisplayContentEmpty(item.before)"
89
+ v-reveal-truncated
90
+ :class="classes.before"
86
91
  ><DisplayRender :content="item.before"
87
92
  /></span>
88
93
  <UIcon
@@ -90,13 +95,13 @@ const classes = computed(
90
95
  :name="icons.arrowRight"
91
96
  :class="classes.arrow"
92
97
  />
93
- <span v-if="!isDisplayContentEmpty(item.after)" :class="classes.after"
98
+ <span v-if="!isDisplayContentEmpty(item.after)" v-reveal-truncated :class="classes.after"
94
99
  ><DisplayRender :content="item.after"
95
100
  /></span>
96
101
  </div>
97
102
  </div>
98
103
  </template>
99
- <div v-else class="overflow-hidden rounded-[var(--nut-display-radius)] border border-default">
104
+ <div v-else :class="classes.frame">
100
105
  <table :class="classes.table">
101
106
  <thead>
102
107
  <tr>
@@ -104,6 +109,7 @@ const classes = computed(
104
109
  v-for="(heading, index) in headings"
105
110
  :key="index"
106
111
  scope="col"
112
+ v-reveal-truncated
107
113
  :class="classes.headCell"
108
114
  >
109
115
  <DisplayRender :content="heading" />
@@ -117,13 +123,19 @@ const classes = computed(
117
123
  :class="item.changed === false ? 'text-dimmed' : ''"
118
124
  :data-changed="item.changed !== false"
119
125
  >
120
- <td :class="[classes.cell, item.changed === false ? '' : 'text-toned']">
126
+ <td
127
+ v-reveal-truncated
128
+ :class="[classes.cell, item.changed === false ? '' : 'text-toned']"
129
+ >
121
130
  <span class="inline-flex flex-wrap items-center gap-1.5">
122
131
  <DisplayRender :content="item.label" />
123
132
  <DisplayChip v-if="item.note" :chip="item.note" :class="classes.note" />
124
133
  </span>
125
134
  </td>
126
- <td :class="[classes.cell, item.changed === false ? '' : 'text-muted']">
135
+ <td
136
+ v-reveal-truncated
137
+ :class="[classes.cell, item.changed === false ? '' : 'text-muted']"
138
+ >
127
139
  <DisplayRender :content="item.before" />
128
140
  </td>
129
141
  <td
@@ -4,6 +4,7 @@ import { computed } from "vue";
4
4
  import { useDisplayAppUi } from "../../composables/use-display-config";
5
5
  import { useDisplayIcons } from "../../composables/use-display-icons";
6
6
  import { useDisplayLocale } from "../../composables/use-display-locale";
7
+ import { vRevealTruncated } from "../../directives/reveal-truncated";
7
8
  import { resolveDisplayClasses } from "../../utils/classes";
8
9
  import { isDisplayContentEmpty } from "../../utils/content";
9
10
  import { displayToneClass } from "../../utils/tone";
@@ -63,10 +64,10 @@ const classes = computed(
63
64
  >
64
65
  <UIcon v-if="variant === 'plain'" :name="icon ?? icons.empty" :class="classes.icon" />
65
66
  <span v-else :class="classes.icon"><UIcon :name="icon ?? icons.empty" class="size-5" /></span>
66
- <div :class="classes.title">
67
+ <div v-reveal-truncated :class="classes.title">
67
68
  <DisplayRender :content="title ?? (variant === 'plain' ? void 0 : t('display.empty'))" />
68
69
  </div>
69
- <div v-if="!isDisplayContentEmpty(description)" :class="classes.description">
70
+ <div v-if="!isDisplayContentEmpty(description)" v-reveal-truncated :class="classes.description">
70
71
  <DisplayRender :content="description" />
71
72
  </div>
72
73
  <DisplayActions v-if="actions?.length" :actions="actions" :class="classes.actions" />
@@ -3,6 +3,7 @@ import UIcon from "@nuxt/ui/components/Icon.vue";
3
3
  import { twMerge } from "tailwind-merge";
4
4
  import { computed } from "vue";
5
5
  import { useDisplayAppUi } from "../../composables/use-display-config";
6
+ import { vRevealTruncated } from "../../directives/reveal-truncated";
6
7
  import { resolveDisplayClasses } from "../../utils/classes";
7
8
  import { isDisplayContentEmpty } from "../../utils/content";
8
9
  import {
@@ -125,40 +126,54 @@ function valueClass(fact) {
125
126
  >
126
127
  <template v-for="(group, groupIndex) in resolvedGroups" :key="groupIndex">
127
128
  <div v-if="!isDisplayContentEmpty(group.title)" :class="classes.groupHeader">
128
- <span :class="classes.groupTitle"><DisplayRender :content="group.title" /></span>
129
- <span v-if="!isDisplayContentEmpty(group.aside)" :class="classes.groupAside">
129
+ <span v-reveal-truncated :class="classes.groupTitle"
130
+ ><DisplayRender :content="group.title"
131
+ /></span>
132
+ <span
133
+ v-if="!isDisplayContentEmpty(group.aside)"
134
+ v-reveal-truncated
135
+ :class="classes.groupAside"
136
+ >
130
137
  <DisplayRender :content="group.aside" />
131
138
  </span>
132
139
  </div>
133
140
  <div v-for="(fact, index) in group.items" :key="index" :class="classes.item">
134
141
  <div class="flex min-w-0 flex-col gap-px">
135
- <span :class="classes.label">
142
+ <span v-reveal-truncated :class="classes.label">
136
143
  <UIcon v-if="fact.icon" :name="fact.icon" class="size-3.5 shrink-0" />
137
144
  <DisplayRender :content="fact.label" />
138
145
  </span>
139
- <span v-if="!isDisplayContentEmpty(fact.hint)" :class="classes.hint">
146
+ <span v-if="!isDisplayContentEmpty(fact.hint)" v-reveal-truncated :class="classes.hint">
140
147
  <DisplayRender :content="fact.hint" />
141
148
  </span>
142
149
  </div>
143
- <div :class="valueClass(fact)" data-display-value><DisplayFactValue :fact="fact" /></div>
150
+ <div v-reveal-truncated :class="valueClass(fact)" data-display-value>
151
+ <DisplayFactValue :fact="fact" />
152
+ </div>
144
153
  </div>
145
154
  </template>
146
155
  </div>
147
156
  <div v-else :class="classes.root" data-display="facts" :data-layout="layout">
148
157
  <div v-for="(group, groupIndex) in resolvedGroups" :key="groupIndex">
149
158
  <div v-if="!isDisplayContentEmpty(group.title)" :class="classes.groupHeader">
150
- <span :class="classes.groupTitle"><DisplayRender :content="group.title" /></span>
151
- <span v-if="!isDisplayContentEmpty(group.aside)" :class="classes.groupAside">
159
+ <span v-reveal-truncated :class="classes.groupTitle"
160
+ ><DisplayRender :content="group.title"
161
+ /></span>
162
+ <span
163
+ v-if="!isDisplayContentEmpty(group.aside)"
164
+ v-reveal-truncated
165
+ :class="classes.groupAside"
166
+ >
152
167
  <DisplayRender :content="group.aside" />
153
168
  </span>
154
169
  </div>
155
170
  <dl :class="['m-0', classes.group]" :style="groupStyle">
156
171
  <div v-for="(fact, index) in group.items" :key="index" :class="classes.item">
157
- <dt :class="classes.label">
172
+ <dt v-reveal-truncated :class="classes.label">
158
173
  <UIcon v-if="fact.icon" :name="fact.icon" class="size-3.5 shrink-0" />
159
174
  <DisplayRender :content="fact.label" />
160
175
  </dt>
161
- <dd :class="['m-0', valueClass(fact)]" data-display-value>
176
+ <dd v-reveal-truncated :class="['m-0', valueClass(fact)]" data-display-value>
162
177
  <DisplayFactValue :fact="fact" />
163
178
  </dd>
164
179
  <dd
@@ -3,8 +3,10 @@ import UButton from "@nuxt/ui/components/Button.vue";
3
3
  import UIcon from "@nuxt/ui/components/Icon.vue";
4
4
  import { computed } from "vue";
5
5
  import { useDisplayAppUi } from "../../composables/use-display-config";
6
+ import { vRevealTruncated } from "../../directives/reveal-truncated";
6
7
  import { resolveDisplayClasses } from "../../utils/classes";
7
8
  import { isDisplayContentEmpty, resolveDisplayText } from "../../utils/content";
9
+ import { isExternalLink } from "../../utils/links";
8
10
  import { displayToneClass } from "../../utils/tone";
9
11
  import DisplayRender from "../parts/display-render";
10
12
  import DisplaySkeleton from "../parts/display-skeleton.vue";
@@ -64,7 +66,8 @@ const actionButton = computed(() => {
64
66
  size: "sm",
65
67
  variant: "link",
66
68
  ...rest,
67
- label: resolveDisplayText(label)
69
+ label: resolveDisplayText(label),
70
+ target: rest.target === void 0 && isExternalLink(rest.to) ? "_blank" : rest.target
68
71
  };
69
72
  });
70
73
  </script>
@@ -80,14 +83,18 @@ const actionButton = computed(() => {
80
83
  <div v-else :class="[classes.root, displayToneClass(tone)]" data-display="heading">
81
84
  <span v-if="tile" :class="classes.icon"><UIcon :name="icon ?? ''" class="size-5" /></span>
82
85
  <UIcon v-else-if="icon" :name="icon" :class="classes.icon" />
83
- <div :class="classes.text">
86
+ <div v-reveal-truncated :class="classes.text">
84
87
  <component :is="`h${level}`" :class="classes.title">
85
88
  <DisplayRender :content="title" />
86
89
  <span v-if="count !== void 0" :class="classes.count">{{
87
90
  resolveDisplayText(count)
88
91
  }}</span>
89
92
  </component>
90
- <div v-if="!isDisplayContentEmpty(description)" :class="classes.description">
93
+ <div
94
+ v-if="!isDisplayContentEmpty(description)"
95
+ v-reveal-truncated
96
+ :class="classes.description"
97
+ >
91
98
  <DisplayRender :content="description" />
92
99
  </div>
93
100
  </div>
@@ -10,6 +10,7 @@ import { useDisplayIcons } from "../../composables/use-display-icons";
10
10
  import { useDisplayLocale } from "../../composables/use-display-locale";
11
11
  import { useDisplayReveal } from "../../composables/use-display-reveal";
12
12
  import { useDisplayUrlActions } from "../../composables/use-display-url";
13
+ import { vRevealTruncated } from "../../directives/reveal-truncated";
13
14
  import { resolveDisplayClasses } from "../../utils/classes";
14
15
  import { displayContentText, isDisplayContentEmpty, resolveDisplayText } from "../../utils/content";
15
16
  import { displayToneClass } from "../../utils/tone";
@@ -160,7 +161,7 @@ function downloadFile(index) {
160
161
  <template>
161
162
  <DisplaySkeleton v-if="loading" :count="Math.min(Math.max(items.length, 2), 5)" />
162
163
  <div v-else :class="classes.root" data-display="list" :data-variant="variant">
163
- <div v-if="!isDisplayContentEmpty(title)" :class="classes.title">
164
+ <div v-if="!isDisplayContentEmpty(title)" v-reveal-truncated :class="classes.title">
164
165
  <DisplayRender :content="title" />
165
166
  </div>
166
167
  <DisplayEmptyLine v-if="!items.length" :content="empty" :class="classes.empty" />
@@ -193,7 +194,7 @@ function downloadFile(index) {
193
194
  {{ files[index]?.label }}
194
195
  </span>
195
196
  <div class="min-w-0 flex-1">
196
- <div :class="classes.text">
197
+ <div v-reveal-truncated :class="classes.text">
197
198
  <a
198
199
  v-if="item.href"
199
200
  :href="isString(item.href) ? item.href : '#'"
@@ -209,6 +210,7 @@ function downloadFile(index) {
209
210
  </div>
210
211
  <div
211
212
  v-if="files[index]?.size || !isDisplayContentEmpty(item.description)"
213
+ v-reveal-truncated
212
214
  :class="classes.description"
213
215
  >
214
216
  {{ files[index]?.size
@@ -274,11 +276,15 @@ function downloadFile(index) {
274
276
  >
275
277
  <DisplayRender :content="item.text" />
276
278
  </div>
277
- <div v-if="!isDisplayContentEmpty(item.description)" :class="classes.description">
279
+ <div
280
+ v-if="!isDisplayContentEmpty(item.description)"
281
+ v-reveal-truncated
282
+ :class="classes.description"
283
+ >
278
284
  <DisplayRender :content="item.description" />
279
285
  </div>
280
286
  </div>
281
- <span v-if="!isDisplayContentEmpty(item.meta)" :class="classes.meta"
287
+ <span v-if="!isDisplayContentEmpty(item.meta)" v-reveal-truncated :class="classes.meta"
282
288
  ><DisplayRender :content="item.meta"
283
289
  /></span>
284
290
  </template>
@@ -8,6 +8,7 @@ import { useDisplayAppUi } from "../../composables/use-display-config";
8
8
  import { useDisplayFilePreview } from "../../composables/use-display-file-preview";
9
9
  import { useDisplayLocale } from "../../composables/use-display-locale";
10
10
  import { useDisplayUrlActions, useResolvedDisplayUrl } from "../../composables/use-display-url";
11
+ import { vRevealTruncated } from "../../directives/reveal-truncated";
11
12
  import { resolveDisplayClasses } from "../../utils/classes";
12
13
  import { isDisplayContentEmpty, resolveDisplayText } from "../../utils/content";
13
14
  import { downloadDisplayUrl, openDisplayUrl } from "../../utils/urls";
@@ -181,7 +182,7 @@ function openLink() {
181
182
  />
182
183
  <span v-else-if="shown.pending.value" class="nut-dash-shimmer absolute inset-0" />
183
184
  </component>
184
- <figcaption v-if="!isDisplayContentEmpty(caption)" :class="classes.caption">
185
+ <figcaption v-if="!isDisplayContentEmpty(caption)" v-reveal-truncated :class="classes.caption">
185
186
  <DisplayRender :content="caption" />
186
187
  </figcaption>
187
188
  </figure>
@@ -208,7 +209,7 @@ function openLink() {
208
209
  >
209
210
  </button>
210
211
  </div>
211
- <figcaption v-if="!isDisplayContentEmpty(caption)" :class="classes.caption">
212
+ <figcaption v-if="!isDisplayContentEmpty(caption)" v-reveal-truncated :class="classes.caption">
212
213
  <DisplayRender :content="caption" />
213
214
  </figcaption>
214
215
  </figure>
@@ -242,6 +243,7 @@ function openLink() {
242
243
  /></span>
243
244
  <span
244
245
  v-if="!isDisplayContentEmpty(title)"
246
+ v-reveal-truncated
245
247
  :class="[classes.title, 'absolute start-3 bottom-2 text-white']"
246
248
  >
247
249
  <DisplayRender :content="title" />
@@ -251,7 +253,7 @@ function openLink() {
251
253
  }}</span>
252
254
  </button>
253
255
  </div>
254
- <figcaption v-if="!isDisplayContentEmpty(caption)" :class="classes.caption">
256
+ <figcaption v-if="!isDisplayContentEmpty(caption)" v-reveal-truncated :class="classes.caption">
255
257
  <DisplayRender :content="caption" />
256
258
  </figcaption>
257
259
  </figure>
@@ -277,9 +279,13 @@ function openLink() {
277
279
  />
278
280
  </div>
279
281
  <div :class="classes.body">
280
- <div :class="classes.name">{{ fileName }}</div>
281
- <div v-if="documentMeta" :class="classes.meta">{{ documentMeta }}</div>
282
- <div v-if="!isDisplayContentEmpty(description)" :class="classes.description">
282
+ <div v-reveal-truncated :class="classes.name">{{ fileName }}</div>
283
+ <div v-if="documentMeta" v-reveal-truncated :class="classes.meta">{{ documentMeta }}</div>
284
+ <div
285
+ v-if="!isDisplayContentEmpty(description)"
286
+ v-reveal-truncated
287
+ :class="classes.description"
288
+ >
283
289
  <DisplayRender :content="description" />
284
290
  </div>
285
291
  <DisplayActions
@@ -301,10 +307,10 @@ function openLink() {
301
307
  @click="isString(href) ? void 0 : openLink()"
302
308
  >
303
309
  <div :class="[classes.body, 'px-3 py-2.5']">
304
- <div v-if="domain" :class="classes.domain">
310
+ <div v-if="domain" v-reveal-truncated :class="classes.domain">
305
311
  <UIcon name="i-lucide-globe" class="size-3.5 shrink-0" />{{ domain }}
306
312
  </div>
307
- <div :class="[classes.title, 'mt-0.5 flex items-center gap-1.5']">
313
+ <div v-reveal-truncated :class="[classes.title, 'mt-0.5 flex items-center gap-1.5']">
308
314
  <DisplayRender :content="title ?? (isString(href) ? href : void 0)" />
309
315
  <UIcon
310
316
  v-if="links.pending.value === 'open'"
@@ -312,7 +318,11 @@ function openLink() {
312
318
  class="size-3.5 shrink-0 animate-spin text-muted"
313
319
  />
314
320
  </div>
315
- <div v-if="!isDisplayContentEmpty(description)" :class="classes.description">
321
+ <div
322
+ v-if="!isDisplayContentEmpty(description)"
323
+ v-reveal-truncated
324
+ :class="classes.description"
325
+ >
316
326
  <DisplayRender :content="description" />
317
327
  </div>
318
328
  </div>