@antelopejs/dms-mailing 0.10.0 → 0.10.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 (44) hide show
  1. package/frontend-vue/app/build/components/FormFieldRow.vue +1 -1
  2. package/frontend-vue/app/build/components/TemplateDrawerPerformance.vue +3 -3
  3. package/frontend-vue/app/build/components/TemplateDrawerVariables.vue +4 -4
  4. package/frontend-vue/app/components/CategoriesInput.vue +2 -2
  5. package/frontend-vue/app/components/CategoryInput.vue +1 -1
  6. package/frontend-vue/app/components/FunnelCard.vue +4 -4
  7. package/frontend-vue/app/components/LocaleSelect.vue +1 -1
  8. package/frontend-vue/app/components/NewTemplateModal.vue +1 -1
  9. package/frontend-vue/app/components/OverviewGate.vue +6 -6
  10. package/frontend-vue/app/components/RealSendModal.vue +6 -6
  11. package/frontend-vue/app/components/RenderingModal.vue +2 -2
  12. package/frontend-vue/app/components/RetentionInput.vue +1 -1
  13. package/frontend-vue/app/components/SendDrawer.vue +9 -9
  14. package/frontend-vue/app/components/StarterGallery.vue +2 -2
  15. package/frontend-vue/app/components/SystemVariables.vue +1 -1
  16. package/frontend-vue/app/components/TemplateCard.vue +2 -2
  17. package/frontend-vue/app/components/TemplateDrawer.vue +4 -4
  18. package/frontend-vue/app/components/TemplateGallery.vue +2 -2
  19. package/frontend-vue/app/components/TestSendModal.vue +4 -4
  20. package/frontend-vue/app/components/VersionPreview.vue +3 -3
  21. package/frontend-vue/app/components/editor/EditorBlockFields.vue +1 -1
  22. package/frontend-vue/app/components/editor/EditorCompare.vue +1 -1
  23. package/frontend-vue/app/components/editor/EditorEnvelope.vue +2 -2
  24. package/frontend-vue/app/components/editor/EditorLifecycle.vue +1 -1
  25. package/frontend-vue/app/components/editor/EditorPaletteGroup.vue +1 -1
  26. package/frontend-vue/app/components/editor/EditorPublishReview.vue +4 -4
  27. package/frontend-vue/app/components/editor/EditorTemplatePane.vue +2 -2
  28. package/frontend-vue/app/components/editor/EditorTestDataPane.vue +1 -1
  29. package/frontend-vue/app/components/editor/EditorVariablesPane.vue +1 -1
  30. package/frontend-vue/app/components/editor/EditorViewSwitches.vue +2 -2
  31. package/frontend-vue/app/components/editor/EditorVisibility.vue +1 -1
  32. package/frontend-vue/package.json +1 -1
  33. package/frontend-vue/tests/categories-input.test.ts +1 -1
  34. package/frontend-vue/tests/dms-component-names.test.ts +12 -0
  35. package/frontend-vue/tests/funnel-card.test.ts +3 -3
  36. package/frontend-vue/tests/helpers/component-tags.ts +58 -0
  37. package/frontend-vue/tests/helpers/nuxt-globals.ts +1 -1
  38. package/frontend-vue/tests/overview-gate.test.ts +1 -1
  39. package/frontend-vue/tests/retention-input.test.ts +2 -2
  40. package/frontend-vue/tests/send-drawer.test.ts +2 -2
  41. package/frontend-vue/tests/send-modals.test.ts +1 -1
  42. package/frontend-vue/tests/template-drawer.test.ts +6 -6
  43. package/frontend-vue/tests/template-gallery.test.ts +3 -3
  44. package/package.json +2 -2
@@ -17,7 +17,7 @@ interface FormFieldRowProps {
17
17
  /** Already translated; marks the control invalid. */
18
18
  error?: string | null
19
19
  required?: boolean
20
- /** The card's side inset: rows of a `DmsSection`. */
20
+ /** The card's side inset: rows of a `DmsSectionPanel`. */
21
21
  inset?: boolean
22
22
  /**
23
23
  * Whether the label names the control the default slot binds the id to;
@@ -32,7 +32,7 @@ const { t } = useI18n()
32
32
  <template>
33
33
  <div class="flex flex-col gap-2">
34
34
  <div class="flex items-center justify-between">
35
- <DmsEyebrow :label="t('dms_mailing.templates.drawer.last_30_days')" />
35
+ <DmsEyebrowLabel :label="t('dms_mailing.templates.drawer.last_30_days')" />
36
36
  <UButton
37
37
  variant="link"
38
38
  size="xs"
@@ -49,7 +49,7 @@ const { t } = useI18n()
49
49
  />
50
50
  </div>
51
51
  <div class="flex flex-col gap-1">
52
- <DmsEyebrow :label="t('dms_mailing.templates.drawer.called_from')" />
52
+ <DmsEyebrowLabel :label="t('dms_mailing.templates.drawer.called_from')" />
53
53
  <p
54
54
  v-if="!hasSends || !performance?.sources.length"
55
55
  class="text-muted text-[12.5px]"
@@ -73,7 +73,7 @@ const { t } = useI18n()
73
73
  />
74
74
  </div>
75
75
  <div class="flex flex-col gap-1">
76
- <DmsEyebrow :label="t('dms_mailing.templates.drawer.fallbacks')" />
76
+ <DmsEyebrowLabel :label="t('dms_mailing.templates.drawer.fallbacks')" />
77
77
  <p v-if="!hasSends" class="text-muted text-[12.5px]">
78
78
  {{ t('dms_mailing.templates.drawer.no_sources') }}
79
79
  </p>
@@ -24,7 +24,7 @@ const { t } = useI18n()
24
24
 
25
25
  <template>
26
26
  <div class="flex flex-col gap-1">
27
- <DmsEyebrow :label="t('dms_mailing.templates.drawer.declared_detected')" />
27
+ <DmsEyebrowLabel :label="t('dms_mailing.templates.drawer.declared_detected')" />
28
28
  <p v-if="!variables.length" class="text-muted text-[12.5px]">
29
29
  {{ t('dms_mailing.templates.drawer.no_variables') }}
30
30
  </p>
@@ -77,7 +77,7 @@ const { t } = useI18n()
77
77
  </span>
78
78
  </div>
79
79
  </div>
80
- <DmsBanner
80
+ <DmsNoticeBanner
81
81
  v-if="hasDraftDeclarations"
82
82
  tone="info"
83
83
  size="sm"
@@ -96,11 +96,11 @@ const { t } = useI18n()
96
96
  @click="openEditor()"
97
97
  />
98
98
  </template>
99
- </DmsBanner>
99
+ </DmsNoticeBanner>
100
100
  <MailingSystemVariables :variables="systemVars" />
101
101
  <div class="flex flex-col gap-1.5">
102
102
  <div class="flex items-center justify-between">
103
- <DmsEyebrow :label="t('dms_mailing.templates.drawer.call_from_code')" />
103
+ <DmsEyebrowLabel :label="t('dms_mailing.templates.drawer.call_from_code')" />
104
104
  <DmsCopyButton :value="snippet" />
105
105
  </div>
106
106
  <pre
@@ -50,7 +50,7 @@ const MOVE_DOWN = 1
50
50
  const FIELD_NAME = 'categories'
51
51
  const MOVE_FIELD_ID = 'moveTo'
52
52
  const MOVE_FIELD_TYPE = 'string'
53
- const MOVE_FIELD_COMPONENT = 'DmsSelect'
53
+ const MOVE_FIELD_COMPONENT = 'DmsInputSelect'
54
54
  const LABEL_PART = 'label'
55
55
  const ROW_FIELD_UI = { error: 'mt-1 ps-9 text-[12px]' }
56
56
 
@@ -293,7 +293,7 @@ watch(
293
293
  />
294
294
  <template #content>
295
295
  <div class="flex flex-col gap-2 p-2.5">
296
- <DmsEyebrow :label="key('pick_icon')" />
296
+ <DmsEyebrowLabel :label="key('pick_icon')" />
297
297
  <div class="grid grid-cols-8 gap-1">
298
298
  <UButton
299
299
  v-for="icon in CATEGORY_ICONS"
@@ -52,7 +52,7 @@ watch(
52
52
  </script>
53
53
 
54
54
  <template>
55
- <DmsSelect
55
+ <DmsInputSelect
56
56
  v-model="selected"
57
57
  :items="items"
58
58
  value-key="value"
@@ -120,9 +120,9 @@ const deltaTitle = (step: FunnelStepReading): string | undefined =>
120
120
  </script>
121
121
 
122
122
  <template>
123
- <DmsCard class="h-full" :padded="false" :title="t(`${KEY_PREFIX}title`)">
123
+ <DmsCardPanel class="h-full" :padded="false" :title="t(`${KEY_PREFIX}title`)">
124
124
  <template #actions>
125
- <DmsSegmented
125
+ <DmsSegmentedControl
126
126
  v-model="kind"
127
127
  size="xs"
128
128
  :items="kindItems"
@@ -155,7 +155,7 @@ const deltaTitle = (step: FunnelStepReading): string | undefined =>
155
155
  >
156
156
  {{ stepLabel(step) }}
157
157
  </span>
158
- <DmsMeter
158
+ <DmsMeterBar
159
159
  as="span"
160
160
  class="@md:order-none @md:col-span-1 order-last"
161
161
  :class="METER_SPAN[layout]"
@@ -208,5 +208,5 @@ const deltaTitle = (step: FunnelStepReading): string | undefined =>
208
208
  {{ takeaway }}
209
209
  </span>
210
210
  </template>
211
- </DmsCard>
211
+ </DmsCardPanel>
212
212
  </template>
@@ -80,7 +80,7 @@ const hint = computed(() =>
80
80
 
81
81
  <template>
82
82
  <div class="flex flex-col gap-1.5">
83
- <DmsSelect
83
+ <DmsInputSelect
84
84
  :model-value="selected || undefined"
85
85
  :items="items"
86
86
  value-key="value"
@@ -339,7 +339,7 @@ async function submit(): Promise<void> {
339
339
 
340
340
  <FormFieldRow :label="t('dms_mailing.templates.cols.category')">
341
341
  <template #default="{ id }">
342
- <DmsSelect
342
+ <DmsInputSelect
343
343
  :id="id"
344
344
  v-model="category"
345
345
  :items="categoryItems"
@@ -83,7 +83,7 @@ function createTemplate(): void {
83
83
  </script>
84
84
 
85
85
  <template>
86
- <DmsCard
86
+ <DmsCardPanel
87
87
  v-if="state === 'loading' && !facts"
88
88
  :padded="false"
89
89
  aria-busy="true"
@@ -92,16 +92,16 @@ function createTemplate(): void {
92
92
  <USkeleton class="h-6 w-56" />
93
93
  <USkeleton v-for="row in SKELETON_ROWS" :key="row" class="h-10 w-full" />
94
94
  </div>
95
- </DmsCard>
95
+ </DmsCardPanel>
96
96
 
97
- <DmsCard v-else-if="state === 'error' && !facts" :padded="false">
97
+ <DmsCardPanel v-else-if="state === 'error' && !facts" :padded="false">
98
98
  <DmsEmptyState
99
99
  variant="error"
100
100
  :title="key('error_title')"
101
101
  :description="key('error_description')"
102
102
  :actions="errorActions"
103
103
  />
104
- </DmsCard>
104
+ </DmsCardPanel>
105
105
 
106
106
  <div v-else class="flex flex-col gap-4">
107
107
  <div v-if="canResume" class="flex justify-end">
@@ -115,7 +115,7 @@ function createTemplate(): void {
115
115
  />
116
116
  </div>
117
117
 
118
- <DmsCard v-if="showsSetup" :padded="false">
118
+ <DmsCardPanel v-if="showsSetup" :padded="false">
119
119
  <div
120
120
  class="grid gap-8 p-6 sm:p-8 lg:grid-cols-[minmax(0,1fr)_minmax(0,1.1fr)]"
121
121
  >
@@ -196,7 +196,7 @@ function createTemplate(): void {
196
196
  </li>
197
197
  </ol>
198
198
  </div>
199
- </DmsCard>
199
+ </DmsCardPanel>
200
200
 
201
201
  <slot v-if="showsDashboard" />
202
202
  </div>
@@ -345,7 +345,7 @@ async function submit(): Promise<void> {
345
345
  novalidate
346
346
  @submit.prevent="submit"
347
347
  >
348
- <DmsBanner
348
+ <DmsNoticeBanner
349
349
  tone="error"
350
350
  icon="i-ph-paper-plane-right"
351
351
  :title="
@@ -362,7 +362,7 @@ async function submit(): Promise<void> {
362
362
 
363
363
  <FormFieldRow :label="t('dms_mailing.test_send.locale')">
364
364
  <template #default="{ id }">
365
- <DmsSelect
365
+ <DmsInputSelect
366
366
  :id="id"
367
367
  v-model="locale"
368
368
  :items="localeItems"
@@ -420,7 +420,7 @@ async function submit(): Promise<void> {
420
420
  </p>
421
421
  </section>
422
422
 
423
- <DmsBanner
423
+ <DmsNoticeBanner
424
424
  v-if="detail && !hasSender"
425
425
  tone="warning"
426
426
  icon="i-ph-at"
@@ -437,7 +437,7 @@ async function submit(): Promise<void> {
437
437
  @click="openSettings"
438
438
  />
439
439
  </template>
440
- </DmsBanner>
440
+ </DmsNoticeBanner>
441
441
 
442
442
  <div class="border-default rounded-lg border px-3">
443
443
  <DmsKeyValueList
@@ -478,7 +478,7 @@ async function submit(): Promise<void> {
478
478
  </DmsKeyValueList>
479
479
  </div>
480
480
 
481
- <DmsCheckbox
481
+ <DmsInputCheckbox
482
482
  v-model="isAcknowledged"
483
483
  :disabled="!recipients.length"
484
484
  :label="
@@ -517,7 +517,7 @@ async function submit(): Promise<void> {
517
517
  </form>
518
518
 
519
519
  <div v-else class="flex flex-col gap-4">
520
- <DmsBanner
520
+ <DmsNoticeBanner
521
521
  tone="warning"
522
522
  icon="i-ph-paper-plane-right"
523
523
  :title="
@@ -116,7 +116,7 @@ onMounted(load)
116
116
  {{ key('description') }}
117
117
  <span class="font-mono text-[12px]">{{ sendIdValue }}</span>
118
118
  </p>
119
- <DmsSegmented
119
+ <DmsSegmentedControl
120
120
  v-model="view"
121
121
  size="xs"
122
122
  :items="viewItems"
@@ -138,7 +138,7 @@ onMounted(load)
138
138
  />
139
139
 
140
140
  <template v-else>
141
- <DmsBanner
141
+ <DmsNoticeBanner
142
142
  v-if="rendering.approximate"
143
143
  tone="warning"
144
144
  size="sm"
@@ -167,7 +167,7 @@ form?.addValidator(guardSubmit)
167
167
  <template>
168
168
  <div class="flex flex-col gap-2">
169
169
  <div class="flex flex-wrap items-center gap-2">
170
- <DmsSegmented
170
+ <DmsSegmentedControl
171
171
  v-model="choice"
172
172
  :items="items"
173
173
  :disabled="disabled"
@@ -424,7 +424,7 @@ onMounted(() => {
424
424
  <template v-else-if="send">
425
425
  <header class="border-default flex flex-col gap-1.5 border-b pb-4">
426
426
  <div class="flex items-center gap-1">
427
- <DmsEyebrow
427
+ <DmsEyebrowLabel
428
428
  class="min-w-0 flex-1"
429
429
  truncate
430
430
  :label="key('eyebrow', { id: shortSendId(send._id) })"
@@ -481,7 +481,7 @@ onMounted(() => {
481
481
  columns-from="md"
482
482
  :items="stats"
483
483
  />
484
- <DmsBanner
484
+ <DmsNoticeBanner
485
485
  v-if="problem"
486
486
  :tone="isResolved ? 'info' : problemTone(problem.kind)"
487
487
  :size="isResolved ? 'sm' : undefined"
@@ -492,7 +492,7 @@ onMounted(() => {
492
492
  "
493
493
  data-problem-banner
494
494
  />
495
- <DmsBanner
495
+ <DmsNoticeBanner
496
496
  v-if="suppression"
497
497
  :tone="suppression.tone"
498
498
  size="sm"
@@ -519,8 +519,8 @@ onMounted(() => {
519
519
  @click="openSuppressionList"
520
520
  />
521
521
  </template>
522
- </DmsBanner>
523
- <DmsBanner
522
+ </DmsNoticeBanner>
523
+ <DmsNoticeBanner
524
524
  v-for="link in linkedSends"
525
525
  :key="link.id"
526
526
  tone="info"
@@ -539,7 +539,7 @@ onMounted(() => {
539
539
  @click="void showSend(link.id)"
540
540
  />
541
541
  </template>
542
- </DmsBanner>
542
+ </DmsNoticeBanner>
543
543
  </section>
544
544
 
545
545
  <!--
@@ -548,7 +548,7 @@ onMounted(() => {
548
548
  -->
549
549
  <div class="@2xl:grid-cols-2 grid gap-6" data-send-details-grid>
550
550
  <section class="flex min-w-0 flex-col gap-2">
551
- <DmsEyebrow :label="key('timeline')" />
551
+ <DmsEyebrowLabel :label="key('timeline')" />
552
552
  <ol class="flex flex-col">
553
553
  <li
554
554
  v-for="item in timeline"
@@ -581,7 +581,7 @@ onMounted(() => {
581
581
  </section>
582
582
 
583
583
  <section class="flex min-w-0 flex-col gap-2">
584
- <DmsEyebrow :label="key('origin')" />
584
+ <DmsEyebrowLabel :label="key('origin')" />
585
585
  <div
586
586
  class="border-default divide-default divide-y overflow-hidden rounded-lg border"
587
587
  >
@@ -623,7 +623,7 @@ onMounted(() => {
623
623
 
624
624
  <section class="flex flex-col gap-2">
625
625
  <div class="flex items-center justify-between gap-2">
626
- <DmsEyebrow :label="key('payload')" />
626
+ <DmsEyebrowLabel :label="key('payload')" />
627
627
  <UButton
628
628
  v-if="payload"
629
629
  size="xs"
@@ -90,7 +90,7 @@ async function createFrom(starter: StarterSummary): Promise<void> {
90
90
  class="rounded-(--dms-radius-card) h-[150px] w-full"
91
91
  />
92
92
  </template>
93
- <DmsCard
93
+ <DmsCardPanel
94
94
  v-for="starter in starters"
95
95
  v-else
96
96
  :key="starter.id"
@@ -131,7 +131,7 @@ async function createFrom(starter: StarterSummary): Promise<void> {
131
131
  {{ starter.slug }}
132
132
  </span>
133
133
  </div>
134
- </DmsCard>
134
+ </DmsCardPanel>
135
135
  </div>
136
136
  <UButton
137
137
  icon="i-ph-plus"
@@ -22,7 +22,7 @@ const { t } = useI18n()
22
22
  v-if="variables.length || checks.length"
23
23
  class="flex min-w-0 flex-col gap-1"
24
24
  >
25
- <DmsEyebrow :label="t(`${KEY}.title`)" />
25
+ <DmsEyebrowLabel :label="t(`${KEY}.title`)" />
26
26
  <p class="text-muted text-[12px] [overflow-wrap:anywhere]">
27
27
  {{ t(`${KEY}.description`) }}
28
28
  </p>
@@ -159,7 +159,7 @@ function chipLabel(code: string, present: boolean): string {
159
159
  </script>
160
160
 
161
161
  <template>
162
- <DmsCard
162
+ <DmsCardPanel
163
163
  as="article"
164
164
  :padded="false"
165
165
  interactive
@@ -302,5 +302,5 @@ function chipLabel(code: string, present: boolean): string {
302
302
  <span v-else class="truncate">{{ footerText }}</span>
303
303
  </div>
304
304
  </div>
305
- </DmsCard>
305
+ </DmsCardPanel>
306
306
  </template>
@@ -451,7 +451,7 @@ function onResolved(preview: PreviewResponse): void {
451
451
  <template v-else-if="template">
452
452
  <div class="flex flex-col gap-1.5 pb-3">
453
453
  <div class="flex items-center gap-1">
454
- <DmsEyebrow :label="t('dms_mailing.templates.drawer.eyebrow')" />
454
+ <DmsEyebrowLabel :label="t('dms_mailing.templates.drawer.eyebrow')" />
455
455
  <div class="ml-auto flex items-center gap-0.5">
456
456
  <template v-if="navigation">
457
457
  <UButton
@@ -569,7 +569,7 @@ function onResolved(preview: PreviewResponse): void {
569
569
 
570
570
  <div v-show="tab === 'preview'" class="flex flex-col gap-3 py-4">
571
571
  <div class="flex items-center justify-between gap-2">
572
- <DmsSegmented
572
+ <DmsSegmentedControl
573
573
  v-if="localeItems.length > 1"
574
574
  v-model="locale"
575
575
  :items="localeItems"
@@ -599,7 +599,7 @@ function onResolved(preview: PreviewResponse): void {
599
599
  />
600
600
  </div>
601
601
  </div>
602
- <DmsBanner
602
+ <DmsNoticeBanner
603
603
  v-if="isLocaleMissing"
604
604
  tone="warning"
605
605
  size="sm"
@@ -629,7 +629,7 @@ function onResolved(preview: PreviewResponse): void {
629
629
  @click="openEditor(locale)"
630
630
  />
631
631
  </template>
632
- </DmsBanner>
632
+ </DmsNoticeBanner>
633
633
  </div>
634
634
 
635
635
  <div v-show="tab === 'variables'" class="flex flex-col gap-4 py-4">
@@ -250,7 +250,7 @@ useEventListener(
250
250
  &#8203;
251
251
  </div>
252
252
  <div class="grid grid-cols-[repeat(auto-fill,minmax(272px,1fr))] gap-4">
253
- <DmsCard
253
+ <DmsCardPanel
254
254
  v-for="index in SKELETON_CARDS"
255
255
  :key="index"
256
256
  :padded="false"
@@ -273,7 +273,7 @@ useEventListener(
273
273
  &#8203;
274
274
  </div>
275
275
  </div>
276
- </DmsCard>
276
+ </DmsCardPanel>
277
277
  </div>
278
278
  </section>
279
279
 
@@ -242,7 +242,7 @@ async function submit(): Promise<void> {
242
242
  :label="t('dms_mailing.real_send.version')"
243
243
  >
244
244
  <template #default="{ id }">
245
- <DmsSelect
245
+ <DmsInputSelect
246
246
  :id="id"
247
247
  v-model="versionChoice"
248
248
  :items="versionItems"
@@ -257,7 +257,7 @@ async function submit(): Promise<void> {
257
257
 
258
258
  <FormFieldRow :label="t('dms_mailing.test_send.locale')">
259
259
  <template #default="{ id }">
260
- <DmsSelect
260
+ <DmsInputSelect
261
261
  :id="id"
262
262
  v-model="locale"
263
263
  :items="localeItems"
@@ -271,7 +271,7 @@ async function submit(): Promise<void> {
271
271
 
272
272
  <FormFieldRow :label="t('dms_mailing.test_send.data')">
273
273
  <template #default="{ id }">
274
- <DmsSelect
274
+ <DmsInputSelect
275
275
  :id="id"
276
276
  v-model="dataChoice"
277
277
  :items="dataItems"
@@ -284,7 +284,7 @@ async function submit(): Promise<void> {
284
284
  </FormFieldRow>
285
285
  </FormRows>
286
286
 
287
- <DmsBanner
287
+ <DmsNoticeBanner
288
288
  tone="info"
289
289
  icon="i-ph-info"
290
290
  :title="t('dms_mailing.test_send.info_title')"
@@ -62,7 +62,7 @@ function onResolved(preview: PreviewResponse): void {
62
62
  <template>
63
63
  <div class="flex flex-col gap-4">
64
64
  <div class="flex items-center justify-between gap-2">
65
- <DmsSegmented
65
+ <DmsSegmentedControl
66
66
  v-if="localeItems.length > 1"
67
67
  v-model="locale"
68
68
  :items="localeItems"
@@ -95,7 +95,7 @@ function onResolved(preview: PreviewResponse): void {
95
95
 
96
96
  <div class="grid gap-4 sm:grid-cols-2">
97
97
  <div class="flex flex-col gap-1.5">
98
- <DmsEyebrow :label="t(`${KEY}.brought`)" />
98
+ <DmsEyebrowLabel :label="t(`${KEY}.brought`)" />
99
99
  <ul v-if="lines.length" class="flex flex-col gap-1 text-[12.5px]">
100
100
  <li
101
101
  v-for="line in lines"
@@ -114,7 +114,7 @@ function onResolved(preview: PreviewResponse): void {
114
114
  </p>
115
115
  </div>
116
116
  <div class="flex flex-col gap-1.5">
117
- <DmsEyebrow :label="t(`${KEY}.declared`)" />
117
+ <DmsEyebrowLabel :label="t(`${KEY}.declared`)" />
118
118
  <p v-if="!declared.length" class="text-muted text-[12.5px]">
119
119
  {{ t(`${KEY}.no_variables`) }}
120
120
  </p>
@@ -91,7 +91,7 @@ function patchChecked(field: BlockField, value: unknown): void {
91
91
  :is-multiline="field.kind === 'tokens'"
92
92
  @update:model-value="patch(field.key, $event)"
93
93
  />
94
- <DmsSegmented
94
+ <DmsSegmentedControl
95
95
  v-else-if="field.kind === 'align'"
96
96
  :model-value="text(field.key) || ALIGNMENTS[0]"
97
97
  :items="alignItems"
@@ -178,7 +178,7 @@ watch(locale, load, { immediate: true })
178
178
  >
179
179
  <div class="flex min-w-0 flex-col gap-0.5">
180
180
  <div class="flex items-center gap-2">
181
- <DmsEyebrow :label="side.label" />
181
+ <DmsEyebrowLabel :label="side.label" />
182
182
  <span
183
183
  v-if="side.preview"
184
184
  class="text-highlighted min-w-0 truncate text-xs font-medium"
@@ -29,7 +29,7 @@ function openEnvelope(): void {
29
29
  class="hover:bg-elevated/50 col-span-2 grid min-h-[38px] w-full grid-cols-subgrid items-center gap-x-2.5 px-3 text-left"
30
30
  @click.stop="openEnvelope()"
31
31
  >
32
- <DmsEyebrow
32
+ <DmsEyebrowLabel
33
33
  :label="t(`${KEY}.subject`)"
34
34
  class="min-w-[78px] whitespace-nowrap"
35
35
  />
@@ -57,7 +57,7 @@ function openEnvelope(): void {
57
57
  class="border-default hover:bg-elevated/50 col-span-2 grid min-h-[38px] w-full grid-cols-subgrid items-center gap-x-2.5 border-t px-3 text-left"
58
58
  @click.stop="openEnvelope()"
59
59
  >
60
- <DmsEyebrow
60
+ <DmsEyebrowLabel
61
61
  :label="t(`${KEY}.preheader`)"
62
62
  class="min-w-[78px] whitespace-nowrap"
63
63
  />
@@ -45,7 +45,7 @@ const buttons = computed(() => lifecycleActions(status.value))
45
45
 
46
46
  <template>
47
47
  <div class="flex flex-col gap-3">
48
- <DmsEyebrow :label="t(`${KEY}.title`)" />
48
+ <DmsEyebrowLabel :label="t(`${KEY}.title`)" />
49
49
  <DmsStatGroup :items="stats" layout="joined" :columns="3" />
50
50
  <div class="flex flex-wrap items-center gap-1.5">
51
51
  <UButton
@@ -37,7 +37,7 @@ useSortableList(root, () => props.entries, {
37
37
 
38
38
  <template>
39
39
  <section>
40
- <DmsEyebrow :label="title" class="mb-2 block" />
40
+ <DmsEyebrowLabel :label="title" class="mb-2 block" />
41
41
  <div ref="root" class="grid grid-cols-2 gap-1.5">
42
42
  <template v-for="element in entries" :key="element.type">
43
43
  <button
@@ -43,7 +43,7 @@ const { t } = useI18n()
43
43
  {{ t(`${KEY}.no_changes`, { version: currentVersion }) }}
44
44
  </p>
45
45
 
46
- <DmsBanner
46
+ <DmsNoticeBanner
47
47
  v-if="blockers.length"
48
48
  tone="error"
49
49
  icon="i-ph-prohibit"
@@ -54,9 +54,9 @@ const { t } = useI18n()
54
54
  <li v-for="blocker in blockers" :key="blocker">{{ blocker }}</li>
55
55
  </ul>
56
56
  </template>
57
- </DmsBanner>
57
+ </DmsNoticeBanner>
58
58
 
59
- <DmsBanner
59
+ <DmsNoticeBanner
60
60
  v-if="warnings.length"
61
61
  tone="warning"
62
62
  icon="i-ph-warning"
@@ -67,7 +67,7 @@ const { t } = useI18n()
67
67
  <li v-for="warning in warnings" :key="warning">{{ warning }}</li>
68
68
  </ul>
69
69
  </template>
70
- </DmsBanner>
70
+ </DmsNoticeBanner>
71
71
 
72
72
  <div class="border-default -mx-1 flex items-center gap-2 border-t pt-4">
73
73
  <UButton
@@ -95,7 +95,7 @@ const preheader = computed({
95
95
 
96
96
  <template v-if="editor.current">
97
97
  <USeparator />
98
- <DmsEyebrow
98
+ <DmsEyebrowLabel
99
99
  :label="t(`${KEY}.envelope`, { locale: editor.locale.toUpperCase() })"
100
100
  />
101
101
  <UFormField :label="t('dms_mailing.editor.subject')" size="sm">
@@ -112,7 +112,7 @@ const preheader = computed({
112
112
 
113
113
  <USeparator />
114
114
  <div class="flex flex-col gap-2">
115
- <DmsEyebrow :label="t('dms_mailing.history.title')" />
115
+ <DmsEyebrowLabel :label="t('dms_mailing.history.title')" />
116
116
  <MailingVersionHistory
117
117
  v-if="session.state.version > 0"
118
118
  :template-id="templateId"
@@ -91,7 +91,7 @@ async function save(): Promise<void> {
91
91
  </div>
92
92
 
93
93
  <USeparator />
94
- <DmsEyebrow :label="t(`${KEY}.checks`)" />
94
+ <DmsEyebrowLabel :label="t(`${KEY}.checks`)" />
95
95
  <DmsCheckList :items="items" size="sm" />
96
96
  <MailingEditorMissingPolicy />
97
97
  </div>
@@ -114,7 +114,7 @@ function insert(path: string): void {
114
114
 
115
115
  <template>
116
116
  <div class="flex flex-col gap-2">
117
- <DmsEyebrow :label="t(`${KEY}.insert_hint`)" />
117
+ <DmsEyebrowLabel :label="t(`${KEY}.insert_hint`)" />
118
118
  <p class="text-muted text-xs">{{ t(`${KEY}.draft_hint`) }}</p>
119
119
 
120
120
  <p v-if="merged.length === 0" class="text-muted text-xs">
@@ -58,7 +58,7 @@ const mode = computed({
58
58
  "
59
59
  >
60
60
  <span class="inline-flex">
61
- <DmsSegmented
61
+ <DmsSegmentedControl
62
62
  v-model="device"
63
63
  size="xs"
64
64
  :items="deviceItems"
@@ -66,7 +66,7 @@ const mode = computed({
66
66
  />
67
67
  </span>
68
68
  </UTooltip>
69
- <DmsSegmented
69
+ <DmsSegmentedControl
70
70
  v-model="mode"
71
71
  size="xs"
72
72
  :items="modeItems"
@@ -66,7 +66,7 @@ const mode = computed({
66
66
  <template>
67
67
  <div class="flex flex-col gap-2">
68
68
  <p class="text-highlighted text-sm font-medium">{{ t(`${KEY}.label`) }}</p>
69
- <DmsSegmented
69
+ <DmsSegmentedControl
70
70
  v-model="mode"
71
71
  size="xs"
72
72
  :items="items"
@@ -6,7 +6,7 @@
6
6
  "license": "Apache-2.0",
7
7
  "packageManager": "pnpm@10.2.0",
8
8
  "engines": {
9
- "@antelopejs/dms-frontend": ">=0.10.0 <0.11.0"
9
+ "@antelopejs/dms-frontend": ">=0.11.0 <0.12.0"
10
10
  },
11
11
  "scripts": {
12
12
  "format": "prettier --write .",
@@ -63,7 +63,7 @@ const GLOBALS = {
63
63
  UFormField: FormFieldStub,
64
64
  UButton: ButtonStub,
65
65
  UPopover: slotStub('UPopover'),
66
- DmsEyebrow: slotStub('DmsEyebrow'),
66
+ DmsEyebrowLabel: slotStub('DmsEyebrowLabel'),
67
67
  DmsInputText: slotStub('DmsInputText'),
68
68
  }
69
69
 
@@ -0,0 +1,12 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { dmsTags, knownComponents } from './helpers/component-tags'
3
+
4
+ describe('the DMS components the layer renders', () => {
5
+ it('names only components the installed DMS or this layer registers', async () => {
6
+ const known = await knownComponents()
7
+ const unknown = (await dmsTags()).filter(
8
+ (use) => !known.has(use.split(': ')[1] ?? ''),
9
+ )
10
+ expect(unknown).toEqual([])
11
+ })
12
+ })
@@ -24,9 +24,9 @@ const EmptyStateStub = defineComponent({
24
24
  })
25
25
 
26
26
  const GLOBALS = {
27
- DmsCard: slotStub('DmsCard'),
28
- DmsSegmented: slotStub('DmsSegmented'),
29
- DmsMeter: slotStub('DmsMeter'),
27
+ DmsCardPanel: slotStub('DmsCardPanel'),
28
+ DmsSegmentedControl: slotStub('DmsSegmentedControl'),
29
+ DmsMeterBar: slotStub('DmsMeterBar'),
30
30
  DmsEmptyState: EmptyStateStub,
31
31
  USkeleton: slotStub('USkeleton'),
32
32
  UIcon: slotStub('UIcon'),
@@ -0,0 +1,58 @@
1
+ import { readdir, readFile } from 'node:fs/promises'
2
+ import { basename, join, sep } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ const LAYER_ROOT = fileURLToPath(new URL('../../', import.meta.url))
6
+ const DMS_LAYERS = fileURLToPath(
7
+ new URL(
8
+ '../../../node_modules/@antelopejs/dms/frontend-vue/layers',
9
+ import.meta.url,
10
+ ),
11
+ )
12
+ const DMS_TAG = /<\/?(Dms[A-Z][\w]*|dms-[a-z][\w-]*)(?=[\s/>])/g
13
+
14
+ async function vueFiles(root: string): Promise<string[]> {
15
+ const paths = await readdir(root, { recursive: true, encoding: 'utf8' })
16
+ return paths
17
+ .filter((path) => path.endsWith('.vue') && !path.includes('node_modules'))
18
+ .map((path) => join(root, path))
19
+ }
20
+
21
+ function pascal(tag: string): string {
22
+ return tag
23
+ .split('-')
24
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
25
+ .join('')
26
+ }
27
+
28
+ /** Names a component directory registers under `prefix`: the prefix and each file's base name, as `dms.frontend.ts` registers them. */
29
+ function registered(prefix: string, files: string[]): string[] {
30
+ return files.map((file) => `${prefix}${basename(file, '.vue')}`)
31
+ }
32
+
33
+ /** Every component the installed DMS layers and this layer's `app/components` register. */
34
+ export async function knownComponents(): Promise<Set<string>> {
35
+ const dms = await vueFiles(DMS_LAYERS)
36
+ const own = await vueFiles(join(LAYER_ROOT, 'app', 'components'))
37
+ return new Set([
38
+ ...registered(
39
+ 'Dms',
40
+ dms.filter((file) => file.includes(`${sep}app${sep}components${sep}`)),
41
+ ),
42
+ ...registered('Mailing', own),
43
+ ])
44
+ }
45
+
46
+ /** Every `Dms*` or `dms-*` tag of this layer's `.vue` files, as `file: Name`, in Pascal case. */
47
+ export async function dmsTags(): Promise<string[]> {
48
+ const files = await vueFiles(join(LAYER_ROOT, 'app'))
49
+ const sources = await Promise.all(
50
+ files.map(async (file) => ({ file, text: await readFile(file, 'utf8') })),
51
+ )
52
+ return sources.flatMap(({ file, text }) =>
53
+ Array.from(
54
+ text.matchAll(DMS_TAG),
55
+ (match) => `${file.slice(LAYER_ROOT.length)}: ${pascal(match[1] ?? '')}`,
56
+ ),
57
+ )
58
+ }
@@ -231,7 +231,7 @@ export const FORM_STUBS: Record<string, Component> = {
231
231
  DmsInputText: InputStub,
232
232
  DmsInputEmail: InputStub,
233
233
  UCheckbox: CheckboxStub,
234
- DmsCheckbox: CheckboxStub,
234
+ DmsInputCheckbox: CheckboxStub,
235
235
  DmsInputTags: TagsStub,
236
236
  UFormField: slotStub('UFormField'),
237
237
  DmsFieldRow: slotStub('DmsFieldRow'),
@@ -57,7 +57,7 @@ const Gate = defineComponent({
57
57
  })
58
58
 
59
59
  const GLOBALS = {
60
- DmsCard: slotStub('DmsCard'),
60
+ DmsCardPanel: slotStub('DmsCardPanel'),
61
61
  DmsEmptyState: EmptyStateStub,
62
62
  DmsIconWell: slotStub('DmsIconWell'),
63
63
  DmsListRow: ListRowStub,
@@ -56,7 +56,7 @@ function mountRetention(): Harness {
56
56
  'onUpdate:modelValue': (value: number) => (state.days = value),
57
57
  }),
58
58
  })
59
- app.component('DmsSegmented', slotStub('DmsSegmented'))
59
+ app.component('DmsSegmentedControl', slotStub('DmsSegmentedControl'))
60
60
  app.component('UInputNumber', slotStub('UInputNumber'))
61
61
  app.component('UAlert', slotStub('UAlert'))
62
62
  app.config.warnHandler = () => undefined
@@ -65,7 +65,7 @@ function mountRetention(): Harness {
65
65
  }
66
66
 
67
67
  function segmented(host: HTMLElement): HTMLElement {
68
- return host.querySelector('[data-stub="DmsSegmented"]') as HTMLElement
68
+ return host.querySelector('[data-stub="DmsSegmentedControl"]') as HTMLElement
69
69
  }
70
70
 
71
71
  describe('RetentionInput', () => {
@@ -29,10 +29,10 @@ const STUB_NAMES = [
29
29
  'USkeleton',
30
30
  'UButton',
31
31
  'UIcon',
32
- 'DmsEyebrow',
32
+ 'DmsEyebrowLabel',
33
33
  'DmsStatusPill',
34
34
  'DmsStatGroup',
35
- 'DmsBanner',
35
+ 'DmsNoticeBanner',
36
36
  'DmsActivityItem',
37
37
  'DmsListRow',
38
38
  'DmsKeyValueList',
@@ -93,7 +93,7 @@ const globals = {
93
93
  ...FORM_STUBS,
94
94
  MailingRecipientsField: RecipientsField,
95
95
  DmsKeyValueList: KeyValueListStub,
96
- DmsBanner: slotStub('DmsBanner'),
96
+ DmsNoticeBanner: slotStub('DmsNoticeBanner'),
97
97
  }
98
98
 
99
99
  let stubs: NuxtStubs
@@ -97,9 +97,9 @@ const DropdownStub = {
97
97
  const globals = {
98
98
  ...FORM_STUBS,
99
99
  UDropdownMenu: DropdownStub,
100
- DmsBanner: slotStub('DmsBanner'),
100
+ DmsNoticeBanner: slotStub('DmsNoticeBanner'),
101
101
  MailingTemplatePreviewFrame: slotStub('MailingTemplatePreviewFrame'),
102
- DmsCard: slotStub('DmsCard'),
102
+ DmsCardPanel: slotStub('DmsCardPanel'),
103
103
  DmsEmptyState: slotStub('DmsEmptyState'),
104
104
  }
105
105
 
@@ -271,7 +271,7 @@ describe('TemplateDrawer', () => {
271
271
  )
272
272
  expect(buttons).not.toContain('dms_mailing.templates.drawer.declare')
273
273
  const banners = [
274
- ...view.host.querySelectorAll('[data-stub="DmsBanner"]'),
274
+ ...view.host.querySelectorAll('[data-stub="DmsNoticeBanner"]'),
275
275
  ].map((banner) => banner.getAttribute('title'))
276
276
  expect(banners).toContain('dms_mailing.templates.drawer.draft_declarations')
277
277
  view.unmount()
@@ -362,7 +362,7 @@ describe('TemplateDrawer', () => {
362
362
  api.templateContent.mockResolvedValue(detail(row('live')))
363
363
  const view = mount(TemplateDrawer, { templateId: 't1' }, globals)
364
364
  await flush()
365
- const segmented = view.host.querySelector('dmssegmented')
365
+ const segmented = view.host.querySelector('dmssegmentedcontrol')
366
366
  expect(segmented).not.toBeNull()
367
367
  expect(
368
368
  view.host.querySelectorAll('dmslistrow')[0]?.getAttribute('title'),
@@ -446,7 +446,7 @@ describe('StarterGallery', () => {
446
446
  api.createTemplate.mockResolvedValue({ _id: 'new-id' })
447
447
  const view = mount(StarterGallery, {}, globals)
448
448
  await flush()
449
- ;(view.host.querySelector('[data-stub="DmsCard"]') as HTMLElement).click()
449
+ ;(view.host.querySelector('[data-stub="DmsCardPanel"]') as HTMLElement).click()
450
450
  await flush()
451
451
  expect(api.createTemplate).toHaveBeenCalledWith({
452
452
  name: 'Order confirmation',
@@ -464,7 +464,7 @@ describe('StarterGallery', () => {
464
464
  api.createTemplate.mockRejectedValue({ statusCode: 409 })
465
465
  const view = mount(StarterGallery, {}, globals)
466
466
  await flush()
467
- ;(view.host.querySelector('[data-stub="DmsCard"]') as HTMLElement).click()
467
+ ;(view.host.querySelector('[data-stub="DmsCardPanel"]') as HTMLElement).click()
468
468
  await flush()
469
469
  expect(stubs.modalCalls[0]?.componentOptions).toEqual({
470
470
  initialName: 'Order confirmation',
@@ -361,7 +361,7 @@ describe('TemplateGallery', () => {
361
361
  const ctx = context({ items: [LIVE] })
362
362
  const view = mount(TemplateGallery, { context: ctx }, globals)
363
363
  await flush()
364
- const card = view.host.querySelector('dmscard') as HTMLElement
364
+ const card = view.host.querySelector('dmscardpanel') as HTMLElement
365
365
  card.dispatchEvent(
366
366
  new KeyboardEvent('keydown', { key: 't', bubbles: true }),
367
367
  )
@@ -426,7 +426,7 @@ describe('TemplateGallery', () => {
426
426
  const skeleton = loading.host.querySelector(
427
427
  '[data-gallery-skeleton]',
428
428
  ) as HTMLElement
429
- const skeletonCard = skeleton.querySelector('dmscard') as HTMLElement
429
+ const skeletonCard = skeleton.querySelector('dmscardpanel') as HTMLElement
430
430
  const skeletonHeading = skeleton.firstElementChild as HTMLElement
431
431
  const skeletonBody = skeletonCard.lastElementChild as HTMLElement
432
432
 
@@ -443,7 +443,7 @@ describe('TemplateGallery', () => {
443
443
  await flush()
444
444
  expect(loaded.host.querySelector('[data-gallery-skeleton]')).toBeNull()
445
445
  const section = loaded.host.querySelector('section') as HTMLElement
446
- const card = section.querySelector('article, dmscard') as HTMLElement
446
+ const card = section.querySelector('article, dmscardpanel') as HTMLElement
447
447
  const body = card.lastElementChild as HTMLElement
448
448
 
449
449
  expect(skeleton.className).toBe(section.className)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antelopejs/dms-mailing",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "E-mail templates for the AntelopeJS DMS: a block editor with drafts and versions, a send log, delivery metrics and a suppression list.",
5
5
  "keywords": [
6
6
  "antelopejs",
@@ -56,7 +56,7 @@
56
56
  "@antelopejs/interface-dms-mailing": ">=0.11.0 <0.12.0",
57
57
  "@antelopejs/interface-email": ">=0.2.0 <0.3.0",
58
58
  "node-cron": "^4.2.1",
59
- "zod": "~3.24.2"
59
+ "zod": "^3.24.2"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@antelopejs/core": "~1.23.2",