nuxt-ui-tools 1.25.0 → 1.26.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/dist/module.json +1 -1
- package/dist/runtime/display/components/blocks/display-accordion.vue +4 -1
- package/dist/runtime/display/components/blocks/display-alert.vue +5 -2
- package/dist/runtime/display/components/blocks/display-breakdown.vue +13 -7
- package/dist/runtime/display/components/blocks/display-chart.vue +6 -2
- package/dist/runtime/display/components/blocks/display-code.vue +2 -1
- package/dist/runtime/display/components/blocks/display-date.vue +21 -16
- package/dist/runtime/display/components/blocks/display-diff.vue +19 -7
- package/dist/runtime/display/components/blocks/display-empty.vue +3 -2
- package/dist/runtime/display/components/blocks/display-facts.vue +24 -9
- package/dist/runtime/display/components/blocks/display-heading.vue +10 -3
- package/dist/runtime/display/components/blocks/display-list.vue +10 -4
- package/dist/runtime/display/components/blocks/display-media.vue +19 -9
- package/dist/runtime/display/components/blocks/display-meter.vue +12 -11
- package/dist/runtime/display/components/blocks/display-person.vue +12 -5
- package/dist/runtime/display/components/blocks/display-record.vue +78 -18
- package/dist/runtime/display/components/blocks/display-stat.vue +16 -5
- package/dist/runtime/display/components/blocks/display-table.vue +4 -0
- package/dist/runtime/display/components/blocks/display-text.vue +8 -2
- package/dist/runtime/display/components/blocks/display-timeline.vue +7 -1
- package/dist/runtime/display/components/parts/display-actions.vue +4 -1
- package/dist/runtime/display/components/parts/display-chip.vue +27 -1
- package/dist/runtime/display/components/parts/display-fact-value.vue +2 -1
- package/dist/runtime/display/components/parts/display-record-media.d.vue.ts +2 -0
- package/dist/runtime/display/components/parts/display-record-media.vue +5 -3
- package/dist/runtime/display/components/parts/display-record-media.vue.d.ts +2 -0
- package/dist/runtime/display/directives/reveal-truncated.d.ts +10 -0
- package/dist/runtime/display/directives/reveal-truncated.js +31 -0
- package/dist/runtime/display/types/blocks/diff.d.ts +1 -1
- package/dist/runtime/display/types/blocks/facts.d.ts +3 -1
- package/dist/runtime/display/types/blocks/person.d.ts +3 -1
- package/dist/runtime/display/types/blocks/record.d.ts +4 -2
- package/dist/runtime/display/types/config.d.ts +9 -2
- package/dist/runtime/display/types/shared.d.ts +9 -0
- package/dist/runtime/display/utils/config.d.ts +7 -2
- package/dist/runtime/display/utils/config.js +11 -1
- package/dist/runtime/display/utils/links.d.ts +12 -0
- package/dist/runtime/display/utils/links.js +9 -0
- package/dist/runtime/file-preview/components/shell/file-preview-header.d.vue.ts +2 -0
- package/dist/runtime/file-preview/components/shell/file-preview-header.vue +13 -7
- package/dist/runtime/file-preview/components/shell/file-preview-header.vue.d.ts +2 -0
- package/dist/runtime/file-preview/components/shell/file-preview-shell.vue +6 -0
- package/dist/runtime/file-preview/components/shell/file-preview-stage.d.vue.ts +5 -3
- package/dist/runtime/file-preview/components/shell/file-preview-stage.vue +4 -10
- package/dist/runtime/file-preview/components/shell/file-preview-stage.vue.d.ts +5 -3
- package/dist/runtime/form/components/root/form.vue +1 -0
- package/dist/runtime/form/composables/use-field-options.js +3 -1
- package/dist/runtime/form/composables/use-form-overlay-controller.js +1 -1
- package/dist/runtime/form/composables/use-form-root.d.ts +3 -1
- package/dist/runtime/form/composables/use-form-root.js +41 -15
- package/dist/runtime/form/composables/use-form-runtime.js +14 -4
- package/dist/runtime/form/composables/use-form-validation.d.ts +5 -2
- package/dist/runtime/form/composables/use-form-validation.js +51 -5
- package/dist/runtime/form/fields/custom-component/component.vue +2 -3
- package/dist/runtime/form/fields/date-family/component.vue +7 -6
- package/dist/runtime/form/fields/date-family/utils.d.ts +5 -0
- package/dist/runtime/form/fields/date-family/utils.js +13 -0
- package/dist/runtime/form/fields/number/component.vue +11 -5
- package/dist/runtime/form/fields/phone-number/component.vue +20 -1
- package/dist/runtime/form/types/api.d.ts +9 -2
- package/dist/runtime/form/types/options.d.ts +4 -1
- package/dist/runtime/form/types/runtime.d.ts +10 -0
- package/dist/runtime/form/types/schema.d.ts +5 -1
- package/dist/runtime/form/utils/api.js +1 -0
- package/dist/runtime/i18n/locales/en.js +12 -3
- package/dist/runtime/i18n/locales/fr.js +12 -3
- package/dist/runtime/i18n/types.d.ts +16 -0
- package/dist/runtime/table/components/filters/tags/date-filter-tag.vue +4 -0
- package/dist/runtime/table/types/filters.d.ts +17 -0
- package/dist/runtime/table/utils/filters/options.js +2 -1
- package/dist/runtime/table/utils/filters/preview/option-preview.js +4 -1
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ import { computed } from "vue";
|
|
|
4
4
|
import { isPlainObject } from "../../../shared/utils/predicate";
|
|
5
5
|
import { useDisplayAppUi } from "../../composables/use-display-config";
|
|
6
6
|
import { useDisplayNumber } from "../../composables/use-display-number";
|
|
7
|
+
import { vRevealTruncated } from "../../directives/reveal-truncated";
|
|
7
8
|
import { resolveDisplayClasses } from "../../utils/classes";
|
|
8
9
|
import { displayContentText, isDisplayContentEmpty, resolveDisplayText } from "../../utils/content";
|
|
9
10
|
import { meterShare, meterTone } from "../../utils/meter";
|
|
@@ -171,16 +172,16 @@ function partTone(partToneName) {
|
|
|
171
172
|
</text>
|
|
172
173
|
</svg>
|
|
173
174
|
<div :class="classes.parts">
|
|
174
|
-
<div v-if="!isDisplayContentEmpty(label)" :class="classes.label">
|
|
175
|
+
<div v-if="!isDisplayContentEmpty(label)" v-reveal-truncated :class="classes.label">
|
|
175
176
|
<DisplayRender :content="label" />
|
|
176
177
|
</div>
|
|
177
178
|
<div v-for="(part, index) in resolvedParts" :key="index" :class="partTone(part.tone)">
|
|
178
179
|
<div :class="classes.header">
|
|
179
|
-
<span :class="classes.label">
|
|
180
|
+
<span v-reveal-truncated :class="classes.label">
|
|
180
181
|
<UIcon v-if="part.icon" :name="part.icon" class="size-3.5" />
|
|
181
182
|
<DisplayRender :content="part.label" />
|
|
182
183
|
</span>
|
|
183
|
-
<span :class="classes.value"
|
|
184
|
+
<span v-reveal-truncated :class="classes.value"
|
|
184
185
|
><DisplayRender :content="part.valueLabel ?? format(part.value)"
|
|
185
186
|
/></span>
|
|
186
187
|
</div>
|
|
@@ -191,15 +192,15 @@ function partTone(partToneName) {
|
|
|
191
192
|
/>
|
|
192
193
|
</div>
|
|
193
194
|
</div>
|
|
194
|
-
<div v-if="!isDisplayContentEmpty(caption)" :class="classes.caption">
|
|
195
|
+
<div v-if="!isDisplayContentEmpty(caption)" v-reveal-truncated :class="classes.caption">
|
|
195
196
|
<DisplayRender :content="caption" />
|
|
196
197
|
</div>
|
|
197
198
|
</div>
|
|
198
199
|
</div>
|
|
199
200
|
<template v-else-if="variant === 'scale'">
|
|
200
201
|
<div :class="classes.header">
|
|
201
|
-
<span :class="classes.label"><DisplayRender :content="label" /></span>
|
|
202
|
-
<span v-if="!isDisplayContentEmpty(valueLabel)" :class="classes.value"
|
|
202
|
+
<span v-reveal-truncated :class="classes.label"><DisplayRender :content="label" /></span>
|
|
203
|
+
<span v-if="!isDisplayContentEmpty(valueLabel)" v-reveal-truncated :class="classes.value"
|
|
203
204
|
><DisplayRender :content="valueLabel"
|
|
204
205
|
/></span>
|
|
205
206
|
</div>
|
|
@@ -227,7 +228,7 @@ function partTone(partToneName) {
|
|
|
227
228
|
{{ resolveDisplayText(level.caption) }}
|
|
228
229
|
</span>
|
|
229
230
|
</div>
|
|
230
|
-
<div v-if="!isDisplayContentEmpty(caption)" :class="classes.caption">
|
|
231
|
+
<div v-if="!isDisplayContentEmpty(caption)" v-reveal-truncated :class="classes.caption">
|
|
231
232
|
<DisplayRender :content="caption" />
|
|
232
233
|
</div>
|
|
233
234
|
</template>
|
|
@@ -252,14 +253,14 @@ function partTone(partToneName) {
|
|
|
252
253
|
:style="{ width: `${part.width}%` }"
|
|
253
254
|
/>
|
|
254
255
|
</div>
|
|
255
|
-
<div v-if="!isDisplayContentEmpty(caption)" :class="classes.caption">
|
|
256
|
+
<div v-if="!isDisplayContentEmpty(caption)" v-reveal-truncated :class="classes.caption">
|
|
256
257
|
<DisplayRender :content="caption" />
|
|
257
258
|
</div>
|
|
258
259
|
</template>
|
|
259
260
|
<template v-else>
|
|
260
261
|
<div v-if="!isDisplayContentEmpty(label) || variant === 'bar'" :class="classes.header">
|
|
261
|
-
<span :class="classes.label"><DisplayRender :content="label" /></span>
|
|
262
|
-
<span :class="classes.value"
|
|
262
|
+
<span v-reveal-truncated :class="classes.label"><DisplayRender :content="label" /></span>
|
|
263
|
+
<span v-reveal-truncated :class="classes.value"
|
|
263
264
|
><DisplayRender :content="variant === 'bar' ? headerValue : valueLabel"
|
|
264
265
|
/></span>
|
|
265
266
|
</div>
|
|
@@ -293,7 +294,7 @@ function partTone(partToneName) {
|
|
|
293
294
|
/></b>
|
|
294
295
|
</span>
|
|
295
296
|
</div>
|
|
296
|
-
<div v-if="!isDisplayContentEmpty(caption)" :class="classes.caption">
|
|
297
|
+
<div v-if="!isDisplayContentEmpty(caption)" v-reveal-truncated :class="classes.caption">
|
|
297
298
|
<DisplayRender :content="caption" />
|
|
298
299
|
</div>
|
|
299
300
|
</template>
|
|
@@ -4,8 +4,10 @@ import UIcon from "@nuxt/ui/components/Icon.vue";
|
|
|
4
4
|
import ULink from "@nuxt/ui/components/Link.vue";
|
|
5
5
|
import { computed } from "vue";
|
|
6
6
|
import { useDisplayAppUi } from "../../composables/use-display-config";
|
|
7
|
+
import { vRevealTruncated } from "../../directives/reveal-truncated";
|
|
7
8
|
import { resolveDisplayClasses } from "../../utils/classes";
|
|
8
9
|
import { isDisplayContentEmpty, resolveDisplayText } from "../../utils/content";
|
|
10
|
+
import { displayLinkAttrs } from "../../utils/links";
|
|
9
11
|
import {
|
|
10
12
|
DISPLAY_SURFACE_CLASSES,
|
|
11
13
|
displayToneClass,
|
|
@@ -110,7 +112,7 @@ function avatarOf(entry) {
|
|
|
110
112
|
:ui="{ fallback: 'font-semibold text-toned', root: 'bg-elevated' }"
|
|
111
113
|
/>
|
|
112
114
|
</div>
|
|
113
|
-
<span v-if="!isDisplayContentEmpty(label)" :class="classes.label"
|
|
115
|
+
<span v-if="!isDisplayContentEmpty(label)" v-reveal-truncated :class="classes.label"
|
|
114
116
|
><DisplayRender :content="label"
|
|
115
117
|
/></span>
|
|
116
118
|
</div>
|
|
@@ -121,8 +123,12 @@ function avatarOf(entry) {
|
|
|
121
123
|
:size="AVATAR_SIZES[variant === 'contact' ? 'contact' : 'user'][size]"
|
|
122
124
|
/>
|
|
123
125
|
<div :class="classes.body">
|
|
124
|
-
<div :class="classes.name">{{ resolveDisplayText(self.name) }}</div>
|
|
125
|
-
<div
|
|
126
|
+
<div v-reveal-truncated :class="classes.name">{{ resolveDisplayText(self.name) }}</div>
|
|
127
|
+
<div
|
|
128
|
+
v-if="!isDisplayContentEmpty(self.description)"
|
|
129
|
+
v-reveal-truncated
|
|
130
|
+
:class="classes.description"
|
|
131
|
+
>
|
|
126
132
|
<DisplayRender :content="self.description" />
|
|
127
133
|
</div>
|
|
128
134
|
</div>
|
|
@@ -133,12 +139,13 @@ function avatarOf(entry) {
|
|
|
133
139
|
<UIcon v-if="contact.icon" :name="contact.icon" :class="classes.contactIcon" />
|
|
134
140
|
<ULink
|
|
135
141
|
v-if="contact.href"
|
|
136
|
-
|
|
142
|
+
v-bind="displayLinkAttrs(contact.href, contact.target)"
|
|
143
|
+
v-reveal-truncated
|
|
137
144
|
:class="[classes.contactValue, 'hover:text-highlighted']"
|
|
138
145
|
>
|
|
139
146
|
{{ contact.value }}
|
|
140
147
|
</ULink>
|
|
141
|
-
<span v-else :class="classes.contactValue">{{ contact.value }}</span>
|
|
148
|
+
<span v-else v-reveal-truncated :class="classes.contactValue">{{ contact.value }}</span>
|
|
142
149
|
<DisplayCopy v-if="contact.copy !== false" :value="contact.value" class="-my-1" />
|
|
143
150
|
</div>
|
|
144
151
|
</div>
|
|
@@ -4,8 +4,10 @@ import ULink from "@nuxt/ui/components/Link.vue";
|
|
|
4
4
|
import { computed } from "vue";
|
|
5
5
|
import { useDisplayAppUi } from "../../composables/use-display-config";
|
|
6
6
|
import { useDisplayReveal } from "../../composables/use-display-reveal";
|
|
7
|
+
import { vRevealTruncated } from "../../directives/reveal-truncated";
|
|
7
8
|
import { resolveDisplayClasses } from "../../utils/classes";
|
|
8
9
|
import { isDisplayContentEmpty } from "../../utils/content";
|
|
10
|
+
import { displayLinkAttrs } from "../../utils/links";
|
|
9
11
|
import { DISPLAY_SURFACE_CLASSES } from "../../utils/tone";
|
|
10
12
|
import DisplayChip from "../parts/display-chip.vue";
|
|
11
13
|
import DisplayEmptyLine from "../parts/display-empty-line.vue";
|
|
@@ -47,6 +49,7 @@ const {
|
|
|
47
49
|
trailingCaption: { type: [String, Number, Function, Object, Boolean, null, Array], required: false, skipCheck: true },
|
|
48
50
|
chip: { type: Object, required: false },
|
|
49
51
|
to: { type: String, required: false },
|
|
52
|
+
target: { type: [String, Object, null], required: false },
|
|
50
53
|
onClick: { type: Function, required: false },
|
|
51
54
|
max: { type: Number, required: false },
|
|
52
55
|
step: { type: Number, required: false },
|
|
@@ -65,6 +68,7 @@ const classes = computed(
|
|
|
65
68
|
header: "flex items-center gap-2 border-b border-default bg-elevated px-3 py-2 text-sm font-semibold text-toned",
|
|
66
69
|
icon: "size-4 shrink-0 text-dimmed",
|
|
67
70
|
item: "flex items-center gap-2.5 border-t border-muted px-3 py-2.5 first:border-t-0",
|
|
71
|
+
avatar: "",
|
|
68
72
|
list: "",
|
|
69
73
|
media: "",
|
|
70
74
|
meta: "mt-0.5 flex min-w-0 items-center gap-1 text-xs text-muted",
|
|
@@ -81,7 +85,7 @@ const classes = computed(
|
|
|
81
85
|
)
|
|
82
86
|
);
|
|
83
87
|
function linkProps(item) {
|
|
84
|
-
if (item.to) return { is: ULink, to
|
|
88
|
+
if (item.to) return { is: ULink, ...displayLinkAttrs(item.to, item.target) };
|
|
85
89
|
if (item.onClick) return { is: "button", type: "button", onClick: item.onClick };
|
|
86
90
|
return { is: "div" };
|
|
87
91
|
}
|
|
@@ -116,17 +120,54 @@ function linkProps(item) {
|
|
|
116
120
|
item.to || item.onClick ? 'w-full text-start hover:bg-elevated' : ''
|
|
117
121
|
]"
|
|
118
122
|
>
|
|
119
|
-
<DisplayRecordMedia
|
|
123
|
+
<DisplayRecordMedia
|
|
124
|
+
:item="item"
|
|
125
|
+
size="sm"
|
|
126
|
+
:class="classes.media"
|
|
127
|
+
:avatar-class="classes.avatar"
|
|
128
|
+
/>
|
|
120
129
|
<div :class="classes.body">
|
|
121
|
-
<div
|
|
122
|
-
|
|
130
|
+
<div
|
|
131
|
+
v-if="!isDisplayContentEmpty(item.reference)"
|
|
132
|
+
v-reveal-truncated
|
|
133
|
+
:class="classes.reference"
|
|
134
|
+
>
|
|
135
|
+
<DisplayRender :content="item.reference" />
|
|
136
|
+
</div>
|
|
137
|
+
<div v-reveal-truncated :class="[classes.title, 'text-sm']">
|
|
138
|
+
<DisplayRender :content="item.title" />
|
|
139
|
+
</div>
|
|
140
|
+
<div v-if="!isDisplayContentEmpty(item.meta)" v-reveal-truncated :class="classes.meta">
|
|
123
141
|
<UIcon v-if="item.metaIcon" :name="item.metaIcon" class="size-3 shrink-0" />
|
|
124
|
-
<span class="truncate"><DisplayRender :content="item.meta" /></span>
|
|
142
|
+
<span v-reveal-truncated class="truncate"><DisplayRender :content="item.meta" /></span>
|
|
143
|
+
</div>
|
|
144
|
+
<div
|
|
145
|
+
v-if="!isDisplayContentEmpty(item.description)"
|
|
146
|
+
v-reveal-truncated
|
|
147
|
+
:class="classes.description"
|
|
148
|
+
>
|
|
149
|
+
<DisplayRender :content="item.description" />
|
|
150
|
+
</div>
|
|
151
|
+
<div v-if="item.badges?.length" :class="classes.badges">
|
|
152
|
+
<DisplayChip
|
|
153
|
+
v-for="(badge, badgeIndex) in item.badges"
|
|
154
|
+
:key="badgeIndex"
|
|
155
|
+
:chip="badge"
|
|
156
|
+
/>
|
|
125
157
|
</div>
|
|
126
158
|
</div>
|
|
127
159
|
<DisplayChip v-if="item.chip" :chip="item.chip" />
|
|
128
|
-
<div v-
|
|
129
|
-
<div :class="classes.trailingValue"
|
|
160
|
+
<div v-if="!isDisplayContentEmpty(item.trailing)" :class="classes.trailing">
|
|
161
|
+
<div v-reveal-truncated :class="classes.trailingValue">
|
|
162
|
+
<DisplayRender :content="item.trailing" />
|
|
163
|
+
</div>
|
|
164
|
+
<div
|
|
165
|
+
v-if="!isDisplayContentEmpty(item.trailingCaption)"
|
|
166
|
+
v-reveal-truncated
|
|
167
|
+
:class="classes.trailingCaption"
|
|
168
|
+
>
|
|
169
|
+
<DisplayRender :content="item.trailingCaption" />
|
|
170
|
+
</div>
|
|
130
171
|
</div>
|
|
131
172
|
</component>
|
|
132
173
|
<DisplayMore
|
|
@@ -147,18 +188,26 @@ function linkProps(item) {
|
|
|
147
188
|
>
|
|
148
189
|
<DisplayRecordMedia :item="single" banner :class="classes.media" />
|
|
149
190
|
<div class="p-3.5">
|
|
150
|
-
<div
|
|
191
|
+
<div
|
|
192
|
+
v-if="!isDisplayContentEmpty(single.reference)"
|
|
193
|
+
v-reveal-truncated
|
|
194
|
+
:class="classes.reference"
|
|
195
|
+
>
|
|
151
196
|
<DisplayRender :content="single.reference" />
|
|
152
197
|
</div>
|
|
153
|
-
<div :class="classes.title"><DisplayRender :content="single.title" /></div>
|
|
154
|
-
<div v-if="!isDisplayContentEmpty(single.meta)" :class="classes.meta">
|
|
198
|
+
<div v-reveal-truncated :class="classes.title"><DisplayRender :content="single.title" /></div>
|
|
199
|
+
<div v-if="!isDisplayContentEmpty(single.meta)" v-reveal-truncated :class="classes.meta">
|
|
155
200
|
<UIcon v-if="single.metaIcon" :name="single.metaIcon" class="size-3 shrink-0" />
|
|
156
|
-
<span class="truncate"><DisplayRender :content="single.meta" /></span>
|
|
201
|
+
<span v-reveal-truncated class="truncate"><DisplayRender :content="single.meta" /></span>
|
|
157
202
|
</div>
|
|
158
203
|
<div v-if="single.badges?.length" :class="classes.badges">
|
|
159
204
|
<DisplayChip v-for="(badge, index) in single.badges" :key="index" :chip="badge" />
|
|
160
205
|
</div>
|
|
161
|
-
<div
|
|
206
|
+
<div
|
|
207
|
+
v-if="!isDisplayContentEmpty(single.description)"
|
|
208
|
+
v-reveal-truncated
|
|
209
|
+
:class="classes.description"
|
|
210
|
+
>
|
|
162
211
|
<DisplayRender :content="single.description" />
|
|
163
212
|
</div>
|
|
164
213
|
</div>
|
|
@@ -177,16 +226,21 @@ function linkProps(item) {
|
|
|
177
226
|
<DisplayRecordMedia
|
|
178
227
|
:item="single"
|
|
179
228
|
:size="variant === 'compact' ? 'sm' : 'lg'"
|
|
229
|
+
:avatar-class="classes.avatar"
|
|
180
230
|
:class="classes.media"
|
|
181
231
|
/>
|
|
182
232
|
<div :class="classes.body">
|
|
183
|
-
<div
|
|
233
|
+
<div
|
|
234
|
+
v-if="!isDisplayContentEmpty(single.reference)"
|
|
235
|
+
v-reveal-truncated
|
|
236
|
+
:class="classes.reference"
|
|
237
|
+
>
|
|
184
238
|
<DisplayRender :content="single.reference" />
|
|
185
239
|
</div>
|
|
186
|
-
<div :class="classes.title"><DisplayRender :content="single.title" /></div>
|
|
187
|
-
<div v-if="!isDisplayContentEmpty(single.meta)" :class="classes.meta">
|
|
240
|
+
<div v-reveal-truncated :class="classes.title"><DisplayRender :content="single.title" /></div>
|
|
241
|
+
<div v-if="!isDisplayContentEmpty(single.meta)" v-reveal-truncated :class="classes.meta">
|
|
188
242
|
<UIcon v-if="single.metaIcon" :name="single.metaIcon" class="size-3 shrink-0" />
|
|
189
|
-
<span class="truncate"><DisplayRender :content="single.meta" /></span>
|
|
243
|
+
<span v-reveal-truncated class="truncate"><DisplayRender :content="single.meta" /></span>
|
|
190
244
|
</div>
|
|
191
245
|
<div v-if="single.badges?.length && variant === 'row'" :class="classes.badges">
|
|
192
246
|
<DisplayChip v-for="(badge, index) in single.badges" :key="index" :chip="badge" />
|
|
@@ -194,8 +248,14 @@ function linkProps(item) {
|
|
|
194
248
|
</div>
|
|
195
249
|
<DisplayChip v-if="single.chip" :chip="single.chip" />
|
|
196
250
|
<div v-if="!isDisplayContentEmpty(single.trailing)" :class="classes.trailing">
|
|
197
|
-
<div :class="classes.trailingValue"
|
|
198
|
-
|
|
251
|
+
<div v-reveal-truncated :class="classes.trailingValue">
|
|
252
|
+
<DisplayRender :content="single.trailing" />
|
|
253
|
+
</div>
|
|
254
|
+
<div
|
|
255
|
+
v-if="!isDisplayContentEmpty(single.trailingCaption)"
|
|
256
|
+
v-reveal-truncated
|
|
257
|
+
:class="classes.trailingCaption"
|
|
258
|
+
>
|
|
199
259
|
<DisplayRender :content="single.trailingCaption" />
|
|
200
260
|
</div>
|
|
201
261
|
</div>
|
|
@@ -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, resolveDisplayText } from "../../utils/content";
|
|
8
9
|
import {
|
|
@@ -117,11 +118,17 @@ const classes = computed(
|
|
|
117
118
|
:key="index"
|
|
118
119
|
:class="[classes.item, item.tone ? displayToneClass(item.tone) : '']"
|
|
119
120
|
>
|
|
120
|
-
<div :class="classes.itemLabel"
|
|
121
|
+
<div v-reveal-truncated :class="classes.itemLabel">
|
|
122
|
+
<DisplayRender :content="item.label" />
|
|
123
|
+
</div>
|
|
121
124
|
<div :class="twMerge(classes.itemValue, item.tone ? 'text-[var(--nut-tone-ink)]' : '')">
|
|
122
125
|
<DisplayRender :content="item.value" />
|
|
123
126
|
</div>
|
|
124
|
-
<div
|
|
127
|
+
<div
|
|
128
|
+
v-if="!isDisplayContentEmpty(item.caption)"
|
|
129
|
+
v-reveal-truncated
|
|
130
|
+
:class="classes.itemCaption"
|
|
131
|
+
>
|
|
125
132
|
<DisplayRender :content="item.caption" />
|
|
126
133
|
</div>
|
|
127
134
|
</div>
|
|
@@ -134,11 +141,11 @@ const classes = computed(
|
|
|
134
141
|
:data-variant="variant"
|
|
135
142
|
>
|
|
136
143
|
<div :class="variant === 'trend' ? 'min-w-0' : 'contents'">
|
|
137
|
-
<div v-if="!isDisplayContentEmpty(label)" :class="classes.label">
|
|
144
|
+
<div v-if="!isDisplayContentEmpty(label)" v-reveal-truncated :class="classes.label">
|
|
138
145
|
<DisplayRender :content="label" />
|
|
139
146
|
</div>
|
|
140
147
|
<div :class="variant === 'trend' ? 'flex items-center gap-2' : 'contents'">
|
|
141
|
-
<div :class="classes.value">
|
|
148
|
+
<div v-reveal-truncated :class="classes.value">
|
|
142
149
|
<DisplayRender :content="value" />
|
|
143
150
|
<span v-if="!isDisplayContentEmpty(unit)" :class="classes.unit"
|
|
144
151
|
><DisplayRender :content="unit"
|
|
@@ -152,7 +159,11 @@ const classes = computed(
|
|
|
152
159
|
<UIcon v-if="status.icon" :name="status.icon" class="size-4 shrink-0" />
|
|
153
160
|
<DisplayRender :content="status.text" />
|
|
154
161
|
</div>
|
|
155
|
-
<div
|
|
162
|
+
<div
|
|
163
|
+
v-if="!isDisplayContentEmpty(caption) || chip"
|
|
164
|
+
v-reveal-truncated
|
|
165
|
+
:class="classes.caption"
|
|
166
|
+
>
|
|
156
167
|
<DisplayChip v-if="chip" :chip="chip" />
|
|
157
168
|
<DisplayRender :content="caption" />
|
|
158
169
|
</div>
|
|
@@ -3,6 +3,7 @@ import { computed } from "vue";
|
|
|
3
3
|
import { isNumber } from "../../../shared/utils/predicate";
|
|
4
4
|
import { useDisplayAppUi } from "../../composables/use-display-config";
|
|
5
5
|
import { useDisplayReveal } from "../../composables/use-display-reveal";
|
|
6
|
+
import { vRevealTruncated } from "../../directives/reveal-truncated";
|
|
6
7
|
import { resolveDisplayClasses } from "../../utils/classes";
|
|
7
8
|
import { isDisplayContentEmpty } from "../../utils/content";
|
|
8
9
|
import { DISPLAY_SURFACE_CLASSES } from "../../utils/tone";
|
|
@@ -84,6 +85,7 @@ function alignClass(column) {
|
|
|
84
85
|
v-for="column in columns"
|
|
85
86
|
:key="column.key"
|
|
86
87
|
scope="col"
|
|
88
|
+
v-reveal-truncated
|
|
87
89
|
:class="[classes.headCell, ...alignClass(column)]"
|
|
88
90
|
:style="column.width ? { width: column.width } : void 0"
|
|
89
91
|
>
|
|
@@ -96,6 +98,7 @@ function alignClass(column) {
|
|
|
96
98
|
<td
|
|
97
99
|
v-for="column in columns"
|
|
98
100
|
:key="column.key"
|
|
101
|
+
v-reveal-truncated
|
|
99
102
|
:class="[classes.cell, ...alignClass(column)]"
|
|
100
103
|
>
|
|
101
104
|
<DisplayRender :content="row[column.key]" />
|
|
@@ -123,6 +126,7 @@ function alignClass(column) {
|
|
|
123
126
|
/>
|
|
124
127
|
<div
|
|
125
128
|
v-if="!isDisplayContentEmpty(caption) || !isDisplayContentEmpty(note)"
|
|
129
|
+
v-reveal-truncated
|
|
126
130
|
:class="classes.meta"
|
|
127
131
|
>
|
|
128
132
|
<span><DisplayRender :content="caption" /></span>
|
|
@@ -4,6 +4,7 @@ import { useResizeObserver } from "@vueuse/core";
|
|
|
4
4
|
import { computed, ref, useTemplateRef } from "vue";
|
|
5
5
|
import { useDisplayAppUi } from "../../composables/use-display-config";
|
|
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";
|
|
@@ -104,11 +105,16 @@ useResizeObserver(body, () => {
|
|
|
104
105
|
:class="classes.header"
|
|
105
106
|
>
|
|
106
107
|
<UIcon v-if="icon" :name="icon" :class="classes.icon" />
|
|
107
|
-
<span :class="classes.title"><DisplayRender :content="title" /></span>
|
|
108
|
+
<span v-reveal-truncated :class="classes.title"><DisplayRender :content="title" /></span>
|
|
108
109
|
</div>
|
|
109
110
|
<UIcon v-if="appearance === 'caption' && icon" :name="icon" :class="classes.icon" />
|
|
110
111
|
<div ref="body" :style="clampStyle">
|
|
111
|
-
<div
|
|
112
|
+
<div
|
|
113
|
+
v-for="(content, index) in blocks"
|
|
114
|
+
:key="index"
|
|
115
|
+
v-reveal-truncated
|
|
116
|
+
:class="classes.paragraph"
|
|
117
|
+
>
|
|
112
118
|
<DisplayRender :content="content" />
|
|
113
119
|
</div>
|
|
114
120
|
</div>
|
|
@@ -6,6 +6,7 @@ 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
8
|
import { useDisplayReveal } from "../../composables/use-display-reveal";
|
|
9
|
+
import { vRevealTruncated } from "../../directives/reveal-truncated";
|
|
9
10
|
import { resolveDisplayClasses } from "../../utils/classes";
|
|
10
11
|
import { isDisplayContentEmpty, resolveDisplayText } from "../../utils/content";
|
|
11
12
|
import { formatDisplayDate, toDisplayDate } from "../../utils/dates";
|
|
@@ -171,6 +172,7 @@ function actorAvatar(name) {
|
|
|
171
172
|
<template v-else>{{ index + 1 }}</template>
|
|
172
173
|
</span>
|
|
173
174
|
<span
|
|
175
|
+
v-reveal-truncated
|
|
174
176
|
:class="[classes.title, entry.resolvedState === 'upcoming' ? 'font-medium text-muted' : '']"
|
|
175
177
|
>
|
|
176
178
|
<DisplayRender :content="entry.title" />
|
|
@@ -198,12 +200,16 @@ function actorAvatar(name) {
|
|
|
198
200
|
/>
|
|
199
201
|
<span :class="[classes.marker, dotClass(entry.resolvedState)]" aria-hidden="true" />
|
|
200
202
|
<div :class="classes.body">
|
|
201
|
-
<div
|
|
203
|
+
<div
|
|
204
|
+
v-reveal-truncated
|
|
205
|
+
:class="[classes.title, entry.resolvedState === 'upcoming' ? 'text-muted' : '']"
|
|
206
|
+
>
|
|
202
207
|
<span class="min-w-0"><DisplayRender :content="entry.title" /></span>
|
|
203
208
|
<time v-if="entry.timeLabel" :class="classes.time">{{ entry.timeLabel }}</time>
|
|
204
209
|
</div>
|
|
205
210
|
<div
|
|
206
211
|
v-if="entry.actor || !isDisplayContentEmpty(entry.description)"
|
|
212
|
+
v-reveal-truncated
|
|
207
213
|
:class="classes.description"
|
|
208
214
|
>
|
|
209
215
|
<span v-if="entry.actor" :class="classes.actor">
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import UButton from "@nuxt/ui/components/Button.vue";
|
|
3
3
|
import { computed } from "vue";
|
|
4
4
|
import { resolveDisplayText } from "../../utils/content";
|
|
5
|
+
import { isExternalLink } from "../../utils/links";
|
|
5
6
|
const { actions, size = "sm" } = defineProps({
|
|
6
7
|
actions: { type: Array, required: true },
|
|
7
8
|
size: { type: null, required: false }
|
|
@@ -12,7 +13,9 @@ const buttons = computed(
|
|
|
12
13
|
size,
|
|
13
14
|
variant: "outline",
|
|
14
15
|
...action,
|
|
15
|
-
label: resolveDisplayText(label)
|
|
16
|
+
label: resolveDisplayText(label),
|
|
17
|
+
// A link out of the app opens in a new tab unless the action sets its own target.
|
|
18
|
+
target: action.target === void 0 && isExternalLink(action.to) ? "_blank" : action.target
|
|
16
19
|
}))
|
|
17
20
|
);
|
|
18
21
|
</script>
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import UBadge from "@nuxt/ui/components/Badge.vue";
|
|
3
|
+
import { twMerge } from "tailwind-merge";
|
|
3
4
|
import { computed } from "vue";
|
|
5
|
+
import { isString } from "../../../shared/utils/predicate";
|
|
6
|
+
import { useDisplayConfig } from "../../composables/use-display-config";
|
|
7
|
+
import { vRevealTruncated } from "../../directives/reveal-truncated";
|
|
4
8
|
import { resolveDisplayText } from "../../utils/content";
|
|
5
9
|
import { displayToneClass, isBuiltinTone } from "../../utils/tone";
|
|
6
10
|
import DisplayRender from "./display-render";
|
|
@@ -20,6 +24,15 @@ const VARIANT_CLASSES = [
|
|
|
20
24
|
"bg-[var(--nut-tone-soft)] text-[var(--nut-tone-ink)] ring ring-inset ring-[var(--nut-tone-line)]"
|
|
21
25
|
]
|
|
22
26
|
];
|
|
27
|
+
const CHIP_SLOTS = [
|
|
28
|
+
"base",
|
|
29
|
+
"label",
|
|
30
|
+
"leadingIcon",
|
|
31
|
+
"leadingAvatar",
|
|
32
|
+
"leadingAvatarSize",
|
|
33
|
+
"trailingIcon"
|
|
34
|
+
];
|
|
35
|
+
const config = useDisplayConfig();
|
|
23
36
|
const badge = computed(() => {
|
|
24
37
|
const { label, tone, ...rest } = chip;
|
|
25
38
|
const variant = rest.variant ?? "soft";
|
|
@@ -30,16 +43,29 @@ const badge = computed(() => {
|
|
|
30
43
|
color: custom ? "neutral" : rest.color ?? tone ?? "neutral",
|
|
31
44
|
label: content === void 0 ? resolveDisplayText(label) : void 0,
|
|
32
45
|
size: rest.size ?? "sm",
|
|
46
|
+
ui: chipUi(rest.ui),
|
|
33
47
|
variant
|
|
34
48
|
};
|
|
35
49
|
});
|
|
50
|
+
function chipUi(own) {
|
|
51
|
+
const app = config.value.ui?.chip;
|
|
52
|
+
if (!app) return own;
|
|
53
|
+
const merged = { ...own };
|
|
54
|
+
for (const slot of CHIP_SLOTS) {
|
|
55
|
+
const appClass = app[slot];
|
|
56
|
+
const ownClass = own?.[slot];
|
|
57
|
+
if (appClass !== void 0 && (ownClass === void 0 || isString(ownClass)))
|
|
58
|
+
merged[slot] = twMerge(appClass, ownClass);
|
|
59
|
+
}
|
|
60
|
+
return merged;
|
|
61
|
+
}
|
|
36
62
|
function variantClass(variant) {
|
|
37
63
|
return VARIANT_CLASSES.find(([name]) => name === variant)?.[1] ?? SOFT;
|
|
38
64
|
}
|
|
39
65
|
</script>
|
|
40
66
|
|
|
41
67
|
<template>
|
|
42
|
-
<UBadge v-bind="badge" :data-tone="chip.tone">
|
|
68
|
+
<UBadge v-reveal-truncated v-bind="badge" :data-tone="chip.tone">
|
|
43
69
|
<DisplayRender v-if="content !== void 0" :content="content" />
|
|
44
70
|
</UBadge>
|
|
45
71
|
</template>
|
|
@@ -3,6 +3,7 @@ import ULink from "@nuxt/ui/components/Link.vue";
|
|
|
3
3
|
import { computed } from "vue";
|
|
4
4
|
import { isString } from "../../../shared/utils/predicate";
|
|
5
5
|
import { displayContentText, isDisplayContentEmpty } from "../../utils/content";
|
|
6
|
+
import { displayLinkAttrs } from "../../utils/links";
|
|
6
7
|
import DisplayChip from "./display-chip.vue";
|
|
7
8
|
import DisplayCopy from "./display-copy.vue";
|
|
8
9
|
import DisplayRender from "./display-render";
|
|
@@ -25,7 +26,7 @@ const copyValue = computed(
|
|
|
25
26
|
<span v-else-if="isDisplayContentEmpty(fact.value)" class="font-medium text-dimmed">—</span>
|
|
26
27
|
<ULink
|
|
27
28
|
v-else-if="fact.to"
|
|
28
|
-
|
|
29
|
+
v-bind="displayLinkAttrs(fact.to, fact.target)"
|
|
29
30
|
class="underline decoration-[var(--ui-border-accented)] underline-offset-2 hover:decoration-current"
|
|
30
31
|
>
|
|
31
32
|
<DisplayRender :content="fact.value" />
|
|
@@ -3,6 +3,8 @@ type __VLS_Props = {
|
|
|
3
3
|
item: DisplayRecordItem;
|
|
4
4
|
size?: 'sm' | 'md' | 'lg';
|
|
5
5
|
banner?: boolean;
|
|
6
|
+
/** Classes of the avatar, from the record's `ui.avatar`. */
|
|
7
|
+
avatarClass?: string;
|
|
6
8
|
};
|
|
7
9
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
8
10
|
declare const _default: typeof __VLS_export;
|
|
@@ -6,11 +6,13 @@ import { displayToneClass } from "../../utils/tone";
|
|
|
6
6
|
const {
|
|
7
7
|
item,
|
|
8
8
|
size = "md",
|
|
9
|
-
banner = false
|
|
9
|
+
banner = false,
|
|
10
|
+
avatarClass = void 0
|
|
10
11
|
} = defineProps({
|
|
11
12
|
item: { type: Object, required: true },
|
|
12
13
|
size: { type: String, required: false },
|
|
13
|
-
banner: { type: Boolean, required: false }
|
|
14
|
+
banner: { type: Boolean, required: false },
|
|
15
|
+
avatarClass: { type: String, required: false }
|
|
14
16
|
});
|
|
15
17
|
const BOX = { lg: "size-13", md: "size-10", sm: "size-8" };
|
|
16
18
|
const ICON = { lg: "size-6", md: "size-5", sm: "size-4" };
|
|
@@ -40,7 +42,7 @@ watch(
|
|
|
40
42
|
v-else-if="item.avatar && !banner"
|
|
41
43
|
v-bind="item.avatar"
|
|
42
44
|
:size="item.avatar.size ?? AVATAR[size]"
|
|
43
|
-
class="shrink-0"
|
|
45
|
+
:class="['shrink-0', avatarClass]"
|
|
44
46
|
/>
|
|
45
47
|
<span
|
|
46
48
|
v-else-if="item.icon || item.image || banner"
|
|
@@ -3,6 +3,8 @@ type __VLS_Props = {
|
|
|
3
3
|
item: DisplayRecordItem;
|
|
4
4
|
size?: 'sm' | 'md' | 'lg';
|
|
5
5
|
banner?: boolean;
|
|
6
|
+
/** Classes of the avatar, from the record's `ui.avatar`. */
|
|
7
|
+
avatarClass?: string;
|
|
6
8
|
};
|
|
7
9
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
8
10
|
declare const _default: typeof __VLS_export;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Directive } from 'vue';
|
|
2
|
+
/**
|
|
3
|
+
* Shows the full text of a clipped element on hover and focus. When the element or one of its
|
|
4
|
+
* descendants cuts its text (`truncate`, `line-clamp`, or any `ui` class that clips), the element
|
|
5
|
+
* gets a `title` with the whole text; the title goes away once nothing is cut. An authored `title`
|
|
6
|
+
* is kept.
|
|
7
|
+
*/
|
|
8
|
+
export declare const vRevealTruncated: Directive<HTMLElement>;
|
|
9
|
+
/** Sets or clears the element's `title` from whether its text is cut right now. */
|
|
10
|
+
export declare function revealTruncated(element: HTMLElement): void;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const OWNED = "data-display-title";
|
|
2
|
+
export const vRevealTruncated = {
|
|
3
|
+
beforeUnmount(element) {
|
|
4
|
+
element.removeEventListener("pointerenter", onReveal);
|
|
5
|
+
element.removeEventListener("focusin", onReveal);
|
|
6
|
+
},
|
|
7
|
+
mounted(element) {
|
|
8
|
+
element.addEventListener("pointerenter", onReveal);
|
|
9
|
+
element.addEventListener("focusin", onReveal);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
function onReveal(event) {
|
|
13
|
+
if (event.currentTarget instanceof HTMLElement) revealTruncated(event.currentTarget);
|
|
14
|
+
}
|
|
15
|
+
export function revealTruncated(element) {
|
|
16
|
+
if (element.hasAttribute("title") && !element.hasAttribute(OWNED)) return;
|
|
17
|
+
const text = element.textContent?.replaceAll(/\s+/gu, " ").trim() ?? "";
|
|
18
|
+
if (text && [element, ...element.querySelectorAll("*")].some(isClipped)) {
|
|
19
|
+
element.setAttribute("title", text);
|
|
20
|
+
element.setAttribute(OWNED, "");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (!element.hasAttribute(OWNED)) return;
|
|
24
|
+
element.removeAttribute("title");
|
|
25
|
+
element.removeAttribute(OWNED);
|
|
26
|
+
}
|
|
27
|
+
function isClipped(element) {
|
|
28
|
+
const style = getComputedStyle(element);
|
|
29
|
+
const cuts = style.textOverflow === "ellipsis" || style.webkitLineClamp !== "" && style.webkitLineClamp !== "none" || style.overflowX === "hidden" || style.overflowY === "hidden";
|
|
30
|
+
return cuts && (element.scrollWidth > element.clientWidth + 1 || element.scrollHeight > element.clientHeight + 1);
|
|
31
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DisplayBlockBase, DisplayChip, DisplayContent } from '../shared.js';
|
|
2
|
-
export type DisplayDiffSlot = 'root' | 'title' | 'item' | 'label' | 'values' | 'before' | 'after' | 'arrow' | 'note' | 'unchanged' | 'table' | 'headCell' | 'cell';
|
|
2
|
+
export type DisplayDiffSlot = 'root' | 'title' | 'item' | 'label' | 'values' | 'before' | 'after' | 'arrow' | 'note' | 'unchanged' | 'frame' | 'table' | 'headCell' | 'cell';
|
|
3
3
|
export interface DisplayDiffItem {
|
|
4
4
|
label: DisplayContent;
|
|
5
5
|
before?: DisplayContent;
|