@radio-garden/ditojs-admin 2.85.2-0.5067ad799

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 (153) hide show
  1. package/README.md +180 -0
  2. package/dist/dito-admin.css +1 -0
  3. package/dist/dito-admin.es.js +12106 -0
  4. package/dist/dito-admin.umd.js +7 -0
  5. package/package.json +96 -0
  6. package/src/DitoAdmin.js +293 -0
  7. package/src/DitoComponent.js +34 -0
  8. package/src/DitoContext.js +318 -0
  9. package/src/DitoTypeComponent.js +42 -0
  10. package/src/DitoUser.js +12 -0
  11. package/src/appState.js +12 -0
  12. package/src/components/DitoAccount.vue +60 -0
  13. package/src/components/DitoAffix.vue +68 -0
  14. package/src/components/DitoAffixes.vue +200 -0
  15. package/src/components/DitoButtons.vue +80 -0
  16. package/src/components/DitoClipboard.vue +186 -0
  17. package/src/components/DitoContainer.vue +374 -0
  18. package/src/components/DitoCreateButton.vue +146 -0
  19. package/src/components/DitoDialog.vue +242 -0
  20. package/src/components/DitoDraggable.vue +117 -0
  21. package/src/components/DitoEditButtons.vue +135 -0
  22. package/src/components/DitoErrors.vue +83 -0
  23. package/src/components/DitoForm.vue +521 -0
  24. package/src/components/DitoFormInner.vue +26 -0
  25. package/src/components/DitoFormNested.vue +17 -0
  26. package/src/components/DitoHeader.vue +84 -0
  27. package/src/components/DitoLabel.vue +200 -0
  28. package/src/components/DitoMenu.vue +186 -0
  29. package/src/components/DitoNavigation.vue +40 -0
  30. package/src/components/DitoNotifications.vue +170 -0
  31. package/src/components/DitoPagination.vue +42 -0
  32. package/src/components/DitoPane.vue +334 -0
  33. package/src/components/DitoPanel.vue +256 -0
  34. package/src/components/DitoPanels.vue +61 -0
  35. package/src/components/DitoRoot.vue +524 -0
  36. package/src/components/DitoSchema.vue +846 -0
  37. package/src/components/DitoSchemaInlined.vue +97 -0
  38. package/src/components/DitoScopes.vue +76 -0
  39. package/src/components/DitoSidebar.vue +50 -0
  40. package/src/components/DitoSpinner.vue +95 -0
  41. package/src/components/DitoTableCell.vue +64 -0
  42. package/src/components/DitoTableHead.vue +121 -0
  43. package/src/components/DitoTabs.vue +103 -0
  44. package/src/components/DitoTrail.vue +124 -0
  45. package/src/components/DitoTreeItem.vue +420 -0
  46. package/src/components/DitoUploadFile.vue +199 -0
  47. package/src/components/DitoVNode.vue +14 -0
  48. package/src/components/DitoView.vue +143 -0
  49. package/src/components/index.js +42 -0
  50. package/src/directives/resize.js +83 -0
  51. package/src/index.js +1 -0
  52. package/src/mixins/ContextMixin.js +68 -0
  53. package/src/mixins/DataMixin.js +131 -0
  54. package/src/mixins/DitoMixin.js +591 -0
  55. package/src/mixins/DomMixin.js +29 -0
  56. package/src/mixins/EmitterMixin.js +158 -0
  57. package/src/mixins/ItemMixin.js +144 -0
  58. package/src/mixins/LoadingMixin.js +23 -0
  59. package/src/mixins/NumberMixin.js +118 -0
  60. package/src/mixins/OptionsMixin.js +304 -0
  61. package/src/mixins/PulldownMixin.js +63 -0
  62. package/src/mixins/ResourceMixin.js +398 -0
  63. package/src/mixins/RouteMixin.js +190 -0
  64. package/src/mixins/SchemaParentMixin.js +33 -0
  65. package/src/mixins/SortableMixin.js +49 -0
  66. package/src/mixins/SourceMixin.js +734 -0
  67. package/src/mixins/TextMixin.js +26 -0
  68. package/src/mixins/TypeMixin.js +280 -0
  69. package/src/mixins/ValidationMixin.js +119 -0
  70. package/src/mixins/ValidatorMixin.js +57 -0
  71. package/src/mixins/ValueMixin.js +31 -0
  72. package/src/styles/_base.scss +17 -0
  73. package/src/styles/_button.scss +191 -0
  74. package/src/styles/_imports.scss +3 -0
  75. package/src/styles/_info.scss +19 -0
  76. package/src/styles/_layout.scss +19 -0
  77. package/src/styles/_pulldown.scss +38 -0
  78. package/src/styles/_scroll.scss +13 -0
  79. package/src/styles/_settings.scss +88 -0
  80. package/src/styles/_table.scss +223 -0
  81. package/src/styles/_tippy.scss +45 -0
  82. package/src/styles/style.scss +9 -0
  83. package/src/types/DitoTypeButton.vue +143 -0
  84. package/src/types/DitoTypeCheckbox.vue +27 -0
  85. package/src/types/DitoTypeCheckboxes.vue +65 -0
  86. package/src/types/DitoTypeCode.vue +199 -0
  87. package/src/types/DitoTypeColor.vue +272 -0
  88. package/src/types/DitoTypeComponent.vue +31 -0
  89. package/src/types/DitoTypeComputed.vue +50 -0
  90. package/src/types/DitoTypeDate.vue +99 -0
  91. package/src/types/DitoTypeLabel.vue +23 -0
  92. package/src/types/DitoTypeList.vue +364 -0
  93. package/src/types/DitoTypeMarkup.vue +700 -0
  94. package/src/types/DitoTypeMultiselect.vue +522 -0
  95. package/src/types/DitoTypeNumber.vue +66 -0
  96. package/src/types/DitoTypeObject.vue +136 -0
  97. package/src/types/DitoTypePanel.vue +18 -0
  98. package/src/types/DitoTypeProgress.vue +40 -0
  99. package/src/types/DitoTypeRadio.vue +45 -0
  100. package/src/types/DitoTypeSection.vue +80 -0
  101. package/src/types/DitoTypeSelect.vue +133 -0
  102. package/src/types/DitoTypeSlider.vue +66 -0
  103. package/src/types/DitoTypeSpacer.vue +11 -0
  104. package/src/types/DitoTypeSwitch.vue +40 -0
  105. package/src/types/DitoTypeText.vue +101 -0
  106. package/src/types/DitoTypeTextarea.vue +48 -0
  107. package/src/types/DitoTypeTreeList.vue +193 -0
  108. package/src/types/DitoTypeUpload.vue +503 -0
  109. package/src/types/index.js +30 -0
  110. package/src/utils/SchemaGraph.js +147 -0
  111. package/src/utils/accessor.js +75 -0
  112. package/src/utils/agent.js +47 -0
  113. package/src/utils/data.js +92 -0
  114. package/src/utils/filter.js +266 -0
  115. package/src/utils/math.js +14 -0
  116. package/src/utils/options.js +48 -0
  117. package/src/utils/path.js +5 -0
  118. package/src/utils/resource.js +44 -0
  119. package/src/utils/route.js +53 -0
  120. package/src/utils/schema.js +1121 -0
  121. package/src/utils/type.js +81 -0
  122. package/src/utils/uid.js +15 -0
  123. package/src/utils/units.js +5 -0
  124. package/src/validators/_creditcard.js +6 -0
  125. package/src/validators/_decimals.js +11 -0
  126. package/src/validators/_domain.js +6 -0
  127. package/src/validators/_email.js +6 -0
  128. package/src/validators/_hostname.js +6 -0
  129. package/src/validators/_integer.js +6 -0
  130. package/src/validators/_max.js +6 -0
  131. package/src/validators/_min.js +6 -0
  132. package/src/validators/_password.js +5 -0
  133. package/src/validators/_range.js +6 -0
  134. package/src/validators/_required.js +9 -0
  135. package/src/validators/_url.js +6 -0
  136. package/src/validators/index.js +12 -0
  137. package/src/verbs.js +17 -0
  138. package/types/index.d.ts +3298 -0
  139. package/types/tests/admin.test-d.ts +27 -0
  140. package/types/tests/component-buttons.test-d.ts +44 -0
  141. package/types/tests/component-list.test-d.ts +159 -0
  142. package/types/tests/component-misc.test-d.ts +137 -0
  143. package/types/tests/component-object.test-d.ts +69 -0
  144. package/types/tests/component-section.test-d.ts +174 -0
  145. package/types/tests/component-select.test-d.ts +107 -0
  146. package/types/tests/components.test-d.ts +81 -0
  147. package/types/tests/context.test-d.ts +31 -0
  148. package/types/tests/fixtures.ts +24 -0
  149. package/types/tests/form.test-d.ts +109 -0
  150. package/types/tests/instance.test-d.ts +20 -0
  151. package/types/tests/schema-features.test-d.ts +402 -0
  152. package/types/tests/variance.test-d.ts +125 -0
  153. package/types/tests/view.test-d.ts +146 -0
@@ -0,0 +1,124 @@
1
+ <template lang="pug">
2
+ .dito-trail
3
+ ul
4
+ li(
5
+ v-for="component in trail"
6
+ )
7
+ a.dito-trail__link(
8
+ :class="{ 'dito-trail__link--active': component.path === $route.path }"
9
+ :href="getComponentHref(component)"
10
+ @click.prevent.stop="onClickComponent(component)"
11
+ )
12
+ span.dito-trail__text(
13
+ :class="{ 'dito-trail__text--dirty': component.isDirty }"
14
+ )
15
+ | {{ component.breadcrumb }}
16
+ slot
17
+ </template>
18
+
19
+ <script>
20
+ import DitoComponent from '../DitoComponent.js'
21
+
22
+ // @vue/component
23
+ export default DitoComponent.component('DitoTrail', {
24
+ computed: {
25
+ trail() {
26
+ return this.appState.routeComponents.filter(
27
+ component => !!component.routeRecord
28
+ )
29
+ }
30
+ },
31
+
32
+ methods: {
33
+ getComponentPath(component) {
34
+ // Do the same as in `DitoMenu`: Link menu items to the first children.
35
+ const { schema } = component
36
+ return schema.type === 'menu'
37
+ ? Object.values(schema.items)[0].fullPath
38
+ : component.path
39
+ },
40
+
41
+ getComponentHref(component) {
42
+ return this.$router.resolve(this.getComponentPath(component)).href
43
+ },
44
+
45
+ onClickComponent(component) {
46
+ this.$router.push({
47
+ path: this.getComponentPath(component),
48
+ force: true
49
+ })
50
+ }
51
+ }
52
+ })
53
+ </script>
54
+
55
+ <style lang="scss">
56
+ @import '../styles/_imports';
57
+
58
+ .dito-trail {
59
+ $self: &;
60
+
61
+ display: flex;
62
+ box-sizing: border-box;
63
+ height: 3em;
64
+
65
+ ul {
66
+ display: flex;
67
+ }
68
+
69
+ li {
70
+ white-space: nowrap;
71
+ }
72
+
73
+ &__link {
74
+ position: relative;
75
+ display: block;
76
+
77
+ &:hover {
78
+ #{$self}__text {
79
+ color: $color-light;
80
+ }
81
+ }
82
+ }
83
+
84
+ &__text--dirty {
85
+ &::after {
86
+ content: '';
87
+ display: inline-block;
88
+ background-color: $color-white;
89
+ width: 8px;
90
+ height: 8px;
91
+ margin: 2px;
92
+ margin-left: 0.5em;
93
+ border-radius: 100%;
94
+ }
95
+ }
96
+
97
+ li:not(:last-child) &__link {
98
+ $angle: 33deg;
99
+
100
+ &::before,
101
+ &::after {
102
+ position: absolute;
103
+ content: '';
104
+ width: 1px;
105
+ height: 0.75em;
106
+ right: -0.25em;
107
+ background: $color-white;
108
+ opacity: 0.5;
109
+ }
110
+
111
+ &::before {
112
+ top: 50%;
113
+ transform: rotate($angle);
114
+ transform-origin: top;
115
+ }
116
+
117
+ &::after {
118
+ bottom: 50%;
119
+ transform: rotate(-$angle);
120
+ transform-origin: bottom;
121
+ }
122
+ }
123
+ }
124
+ </style>
@@ -0,0 +1,420 @@
1
+ <template lang="pug">
2
+ .dito-tree-item(
3
+ :id="dataPath"
4
+ :class=`{
5
+ 'dito-tree-item--dragging': isDragging,
6
+ 'dito-tree-item--active': active
7
+ }`
8
+ :style="level > 0 && { '--level': level }"
9
+ :data-path="path"
10
+ )
11
+ .dito-tree-header(
12
+ v-if="label"
13
+ )
14
+ .dito-tree-branch(
15
+ v-if="numEntries"
16
+ @click.stop="opened = !opened"
17
+ )
18
+ .dito-chevron(
19
+ v-if="numEntries"
20
+ :class="{ 'dito-chevron--open': opened }"
21
+ )
22
+ .dito-tree-label(
23
+ v-html="label"
24
+ )
25
+ .dito-tree-info(
26
+ v-if="details"
27
+ ) {{ details }}
28
+ .dito-tree-leaf(
29
+ v-else
30
+ )
31
+ .dito-tree-label(
32
+ v-html="label"
33
+ )
34
+ .dito-buttons.dito-buttons--small(
35
+ v-if="hasEditButtons"
36
+ )
37
+ //- Firefox doesn't like <button> here, so use <a> instead:
38
+ a.dito-button(
39
+ v-if="draggable"
40
+ v-bind="getButtonAttributes(verbs.drag)"
41
+ )
42
+ button.dito-button(
43
+ v-if="editable"
44
+ type="button"
45
+ v-bind="getButtonAttributes(verbs.edit)"
46
+ @click="onEdit"
47
+ )
48
+ button.dito-button(
49
+ v-if="deletable"
50
+ type="button"
51
+ v-bind="getButtonAttributes(verbs.delete)"
52
+ @click="onDelete"
53
+ )
54
+ table.dito-properties(
55
+ v-if="properties"
56
+ v-show="opened"
57
+ )
58
+ tr(
59
+ v-for="property in properties"
60
+ )
61
+ td
62
+ DitoLabel(
63
+ v-if="property.label !== false"
64
+ :dataPath="getPropertyDataPath(property)"
65
+ :label="getLabel(property)"
66
+ )
67
+ DitoTableCell(
68
+ :cell="property"
69
+ :schema="property"
70
+ :dataPath="getPropertyDataPath(property)"
71
+ :data="data"
72
+ :meta="nestedMeta"
73
+ :store="store"
74
+ :disabled="disabled"
75
+ )
76
+ DitoDraggable(
77
+ v-if="childrenSchema"
78
+ v-show="opened"
79
+ :options="getDraggableOptions(true)"
80
+ :draggable="childrenDraggable"
81
+ :modelValue="updateOrder(childrenSchema, childrenList)"
82
+ @update:modelValue="value => (childrenList = value)"
83
+ )
84
+ DitoTreeItem(
85
+ v-for="(item, index) in childrenItems"
86
+ :key="getItemUid(childrenSchema, item.data)"
87
+ :schema="childrenSchema"
88
+ :dataPath="getItemDataPath(childrenSchema, index)"
89
+ :data="item.data"
90
+ :path="item.path"
91
+ :open="item.open"
92
+ :active="item.active"
93
+ :draggable="childrenDraggable"
94
+ :label="getItemLabel(childrenSchema, item.data, { index })"
95
+ :level="level + 1"
96
+ )
97
+ //- TODO: Convert dito-tree-item to use dito-label internally, and then
98
+ //- pass `asObject: true` in the `getItemLabel()` call above.
99
+ </template>
100
+
101
+ <script>
102
+ import DitoComponent from '../DitoComponent.js'
103
+ import ItemMixin from '../mixins/ItemMixin'
104
+ import SortableMixin from '../mixins/SortableMixin.js'
105
+ import { appendDataPath } from '../utils/data.js'
106
+ import { getSchemaAccessor } from '../utils/accessor.js'
107
+ import { getNamedSchemas, hasFormSchema } from '../utils/schema.js'
108
+
109
+ // @vue/component
110
+ export default DitoComponent.component('DitoTreeItem', {
111
+ mixins: [ItemMixin, SortableMixin],
112
+ emits: ['update:data'],
113
+ inject: ['container'],
114
+
115
+ props: {
116
+ schema: { type: Object, required: true },
117
+ dataPath: { type: String, required: true },
118
+ data: { type: [Array, Object], default: null },
119
+ path: { type: String, default: '' },
120
+ open: { type: Boolean, default: false },
121
+ active: { type: Boolean, default: false },
122
+ draggable: { type: Boolean, default: false },
123
+ label: { type: String, default: null },
124
+ level: { type: Number, default: 0 }
125
+ },
126
+
127
+ data() {
128
+ return {
129
+ opened: this.open || this.schema.open
130
+ }
131
+ },
132
+
133
+ computed: {
134
+ meta() {
135
+ return this.container.meta
136
+ },
137
+
138
+ store() {
139
+ return this.container.store
140
+ },
141
+
142
+ disabled() {
143
+ return this.container.disabled
144
+ },
145
+
146
+ nestedMeta() {
147
+ return {
148
+ ...this.meta,
149
+ schema: this.schema
150
+ }
151
+ },
152
+
153
+ properties() {
154
+ return getNamedSchemas(this.schema.properties)
155
+ },
156
+
157
+ // TODO: Should this be named `sourceSchema` instead? Use SourceMixin?
158
+ childrenSchema() {
159
+ return this.schema.children
160
+ },
161
+
162
+ childrenList: {
163
+ get() {
164
+ const name = this.childrenSchema?.name
165
+ return name && this.data[name]
166
+ },
167
+
168
+ set(value) {
169
+ const name = this.childrenSchema?.name
170
+ if (name) {
171
+ this.updateOrder(this.childrenSchema, value)
172
+ // eslint-disable-next-line vue/no-mutating-props
173
+ this.data[name] = value
174
+ this.$emit('update:data', value)
175
+ }
176
+ }
177
+ },
178
+
179
+ childrenDraggable() {
180
+ return (
181
+ this.childrenList?.length > 1 &&
182
+ this.getSchemaValue('draggable', {
183
+ type: Boolean,
184
+ default: false,
185
+ schema: this.childrenSchema
186
+ })
187
+ )
188
+ },
189
+
190
+ numChildren() {
191
+ return this.childrenList?.length || 0
192
+ },
193
+
194
+ numProperties() {
195
+ return this.properties?.length || 0
196
+ },
197
+
198
+ numEntries() {
199
+ return this.numProperties + this.numChildren
200
+ },
201
+
202
+ childrenItems() {
203
+ const { childrenSchema, childrenList } = this
204
+ if (childrenSchema && childrenList) {
205
+ const { editPath } = this.container
206
+ const childrenOpen = !this.path && childrenSchema.open
207
+ // Build a children list with child meta information for the template.
208
+ return childrenList.map((data, index) => {
209
+ const path = (
210
+ childrenSchema.path &&
211
+ `${this.path}/${childrenSchema.path}/${index}`
212
+ )
213
+ const open = (
214
+ childrenOpen ||
215
+ // Only count as "in edit path" when it's not the full edit path.
216
+ editPath.startsWith(path) && path.length < editPath.length
217
+ )
218
+ const active = editPath === path
219
+ return { data, path, open, active }
220
+ })
221
+ }
222
+ return []
223
+ },
224
+
225
+ details() {
226
+ const { numChildren } = this
227
+ return (
228
+ numChildren &&
229
+ `${numChildren} ${
230
+ numChildren === 1 ? 'item' : 'items'
231
+ }`
232
+ )
233
+ },
234
+
235
+ hasEditButtons() {
236
+ return this.draggable || this.editable || this.deletable
237
+ },
238
+
239
+ // TODO: Support creatable!
240
+ // TODO: Add support for creatable, editable and deletable overrides on the
241
+ // associated forms, just like in `TypeList` and `TypeObject`, through
242
+ // `DitoEditButtons`. It would be best to use `DitoEditButtons` here too.
243
+ creatable: getSchemaAccessor('creatable', {
244
+ type: Boolean,
245
+ default: false,
246
+ get(creatable) {
247
+ return creatable && hasFormSchema(this.schema)
248
+ }
249
+ }),
250
+
251
+ editable: getSchemaAccessor('editable', {
252
+ type: Boolean,
253
+ default: false,
254
+ get(editable) {
255
+ return editable && hasFormSchema(this.schema)
256
+ }
257
+ }),
258
+
259
+ deletable: getSchemaAccessor('deletable', {
260
+ type: Boolean,
261
+ default: false
262
+ })
263
+ },
264
+
265
+ methods: {
266
+ getPropertyDataPath(property) {
267
+ return appendDataPath(this.dataPath, property.name)
268
+ },
269
+
270
+ editPath(path) {
271
+ // All we got to do is push the right edit path to the router, the rest
272
+ // is handled by our routes, allowing reloads as well.
273
+ this.$router.push({
274
+ path: `${this.container.path}${path}`,
275
+ // Preserve current query
276
+ query: this.$route.query
277
+ })
278
+ },
279
+
280
+ onEdit() {
281
+ this.editPath(this.path)
282
+ },
283
+
284
+ onDelete() {
285
+ // TODO: Implement!
286
+ },
287
+
288
+ onChange() {
289
+ this.container.onChange()
290
+ },
291
+
292
+ // @override
293
+ onEndDrag(event) {
294
+ SortableMixin.methods.onEndDrag.call(this, event)
295
+ const { item } = event
296
+ // Preserve active state of edited sub-items, by editing their new path.
297
+ if (item.classList.contains('dito-tree-item--active')) {
298
+ this.$nextTick(() => {
299
+ this.editPath(event.item.dataset.path)
300
+ })
301
+ }
302
+ }
303
+ }
304
+ })
305
+ </script>
306
+
307
+ <style lang="scss">
308
+ @import '../styles/_imports';
309
+
310
+ .dito-tree-item {
311
+ --chevron-indent: #{$chevron-indent};
312
+
313
+ overflow: hidden;
314
+
315
+ > .dito-tree-header {
316
+ > .dito-tree-branch,
317
+ > .dito-tree-leaf {
318
+ // Use `--level` CSS variable to calculated the accumulated indent
319
+ // padding directly instead of having it accumulate in nested CSS.
320
+ // This way, we can keep the &--active area cover the full width:
321
+ padding-left: calc(var(--chevron-indent) * (var(--level, 1) - 1));
322
+ }
323
+ }
324
+
325
+ .dito-tree-branch {
326
+ cursor: pointer;
327
+ }
328
+
329
+ .dito-tree-header {
330
+ display: flex;
331
+ justify-content: space-between;
332
+ }
333
+
334
+ .dito-tree-branch,
335
+ .dito-tree-leaf {
336
+ display: flex;
337
+ flex: auto;
338
+ overflow: hidden;
339
+ min-width: 0;
340
+ position: relative;
341
+ margin: 1px 0;
342
+ @include user-select(none);
343
+ }
344
+
345
+ .dito-tree-label {
346
+ display: flex;
347
+ align-items: baseline;
348
+ gap: 0.25em;
349
+ overflow: hidden;
350
+ white-space: nowrap;
351
+ }
352
+
353
+ .dito-tree-info {
354
+ white-space: nowrap;
355
+ }
356
+
357
+ .dito-tree-info {
358
+ padding-left: 0.35em;
359
+ color: rgba($color-black, 0.2);
360
+ }
361
+
362
+ .dito-buttons {
363
+ flex: auto;
364
+ display: flex;
365
+ visibility: hidden;
366
+ height: 100%;
367
+ margin: 1px;
368
+ margin-right: 0;
369
+ }
370
+
371
+ .dito-tree-header:hover {
372
+ > .dito-buttons {
373
+ visibility: visible;
374
+ } // Hide buttons during dragging
375
+ }
376
+
377
+ &--dragging {
378
+ .dito-tree-header {
379
+ > .dito-buttons {
380
+ visibility: hidden;
381
+ }
382
+ }
383
+ }
384
+
385
+ &--active {
386
+ > .dito-tree-header {
387
+ background: $color-active;
388
+ padding: 0 $input-padding-hor;
389
+ margin: 0 (-$input-padding-hor);
390
+
391
+ > .dito-tree-branch {
392
+ > .dito-chevron::before {
393
+ color: $color-white;
394
+ }
395
+ }
396
+
397
+ > * > .dito-tree-label {
398
+ color: $color-white;
399
+ }
400
+ }
401
+ }
402
+
403
+ .dito-properties {
404
+ display: block;
405
+ margin-left: $chevron-indent;
406
+
407
+ > tr {
408
+ vertical-align: baseline;
409
+ }
410
+
411
+ .dito-label {
412
+ margin: 0;
413
+
414
+ &::after {
415
+ content: ': ';
416
+ }
417
+ }
418
+ }
419
+ }
420
+ </style>