@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,318 @@
1
+ import { toRaw } from 'vue'
2
+ import { getValueAtDataPath, isFunction } from '@ditojs/utils'
3
+ import {
4
+ getItemDataPath,
5
+ getParentItemDataPath,
6
+ getParentItem,
7
+ getItem,
8
+ getLastDataPathName,
9
+ getLastDataPathIndex
10
+ } from './utils/data.js'
11
+
12
+ const { hasOwnProperty } = Object.prototype
13
+
14
+ // See also `ContextMixin`: `DitoContext` instances are a thin wrapper around
15
+ // raw `context` objects, which themselves actually inherit from the linked
16
+ // `component` instance, so that they only need to provide the values that
17
+ // should be different than in the underlying component. In order to not expose
18
+ // all fields from the component, the wrapper is introduced.
19
+
20
+ // Use WeakMap for the raw `context` objects, so we don't have to pollute the
21
+ // actual `DitoContext` instance with it.
22
+ const contexts = new WeakMap()
23
+
24
+ function toObject(context) {
25
+ const rawStart = toRaw(context)
26
+ let raw = rawStart
27
+ let object = null
28
+ // In case `DitoContext.extend()` was used, we need to find the actual context
29
+ // object from the object's the inheritance chain:
30
+ do {
31
+ object = contexts.get(raw)
32
+ if (object) break
33
+ raw = Object.getPrototypeOf(raw)
34
+ } while (raw)
35
+ if (object && raw !== rawStart) {
36
+ // Assign the passed context with the original object as well, so we don't
37
+ // have to search for it again:
38
+ contexts.set(raw, object)
39
+ }
40
+ return object
41
+ }
42
+
43
+ function get(context, key, defaultValue) {
44
+ const object = toObject(context)
45
+ const value = key in object ? object[key] : undefined
46
+ // If `object` explicitly sets the key to `undefined`, return it.
47
+ return value !== undefined || hasOwnProperty.call(object, key)
48
+ ? value
49
+ : isFunction(defaultValue)
50
+ ? defaultValue()
51
+ : defaultValue
52
+ }
53
+
54
+ function set(context, key, value) {
55
+ toObject(context)[key] = value
56
+ }
57
+
58
+ export default class DitoContext {
59
+ constructor(component, context) {
60
+ // Use the provided params object / function, or create a new one:
61
+ context = context
62
+ ? isFunction(context)
63
+ ? context()
64
+ : { ...context }
65
+ : {}
66
+ // If not explicitly set (to false), default to true so we don't fall back
67
+ // to `component` for its value.
68
+ context.nested ??= true
69
+ context.component = component
70
+ // Have `object` inherit from the `component` instance, so it can override
71
+ // its values and still retrieve from it, and associate it with `this`
72
+ // through `contexts` map:
73
+ const object = Object.setPrototypeOf(context, component)
74
+ // No need for `toRaw(this)` here as it's always raw inside the constructor.
75
+ contexts.set(this, object)
76
+ }
77
+
78
+ static get(component, context) {
79
+ return context instanceof DitoContext
80
+ ? context
81
+ : new DitoContext(component, context)
82
+ }
83
+
84
+ extend(object) {
85
+ // Create a copy of this context that inherits from the real one, but
86
+ // overrides some properties with the ones from the passed `object`.
87
+ return Object.setPrototypeOf(object, this)
88
+ }
89
+
90
+ // `nested` is `true` when the data-path points a value inside an item, and
91
+ // `false` when it points to the item itself.
92
+ get nested() {
93
+ return get(this, 'nested', true)
94
+ }
95
+
96
+ get schema() {
97
+ return get(this, 'schema', null)
98
+ }
99
+
100
+ get value() {
101
+ return get(
102
+ this,
103
+ 'value',
104
+ () =>
105
+ // If the value is not defined on the underlying object, it's not a type
106
+ // component. If it is nested, we can still get the value from the root.
107
+ this.nested
108
+ ? getValueAtDataPath(this.rootItem, this.dataPath)
109
+ : undefined
110
+ )
111
+ }
112
+
113
+ get dataPath() {
114
+ return get(this, 'dataPath', '')
115
+ }
116
+
117
+ get name() {
118
+ return get(this, 'name', () => getLastDataPathName(this.dataPath))
119
+ }
120
+
121
+ get index() {
122
+ return get(this, 'index', () => getLastDataPathIndex(this.dataPath))
123
+ }
124
+
125
+ get itemDataPath() {
126
+ return getItemDataPath(this.dataPath, this.nested)
127
+ }
128
+
129
+ get parentItemDataPath() {
130
+ return getParentItemDataPath(this.dataPath, this.nested)
131
+ }
132
+
133
+ get itemIndex() {
134
+ return getLastDataPathIndex(this.itemDataPath)
135
+ }
136
+
137
+ get parentItemIndex() {
138
+ return getLastDataPathIndex(this.parentItemDataPath)
139
+ }
140
+
141
+ // NOTE: While internally, we speak of `data`, in the API surface the
142
+ // term `item` is used for the data that relates to editing objects:
143
+ get item() {
144
+ return get(
145
+ this,
146
+ 'data',
147
+ // If `data` isn't provided, we can determine it from rootData & dataPath:
148
+ () => getItem(this.rootItem, this.dataPath, this.nested) || null
149
+ )
150
+ }
151
+
152
+ // NOTE: `parentItem` isn't the closest data parent to `item`, it's the
153
+ // closest parent that isn't an array, e.g. for relations or nested JSON data.
154
+ // This is why the term `item` was chosen over `data`, e.g. VS the use of
155
+ // `parentData` in server-sided validation, which is the closest parent. If
156
+ // needed, we could expose this data here too, as we can do all sorts of data
157
+ // processing with `rootData` and `dataPath`.
158
+ get parentItem() {
159
+ const item = (
160
+ getParentItem(this.rootItem, this.dataPath, this.nested) || null
161
+ )
162
+ return item !== this.item ? item : null
163
+ }
164
+
165
+ get rootItem() {
166
+ return get(this, 'rootData', null)
167
+ }
168
+
169
+ get processedItem() {
170
+ return get(this, 'processedData', null)
171
+ }
172
+
173
+ get processedRootItem() {
174
+ return get(this, 'processedRootData', null)
175
+ }
176
+
177
+ get clipboardItem() {
178
+ return get(this, 'clipboardData', null)
179
+ }
180
+
181
+ get user() {
182
+ return get(this, 'user', null)
183
+ }
184
+
185
+ get api() {
186
+ return get(this, 'api', null)
187
+ }
188
+
189
+ get views() {
190
+ return get(this, 'views', null)
191
+ }
192
+
193
+ get flattenedViews() {
194
+ return get(this, 'flattenedViews', null)
195
+ }
196
+
197
+ get itemLabel() {
198
+ return get(this, 'itemLabel', null)
199
+ }
200
+
201
+ get formLabel() {
202
+ return get(this, 'formLabel', null)
203
+ }
204
+
205
+ // NOTE: Like 'options', the associated component doesn't always exist, e.g.
206
+ // in nested forms during `processData()`. But `schema` is guaranteed to
207
+ // always be available.
208
+ get component() {
209
+ return get(this, 'component', null)
210
+ }
211
+
212
+ // TODO: Add `componentSchema` getter for the schema of the current component,
213
+ // even when the component isn't actually instantiated. Consider adding
214
+ // `sourceSchema` as well?
215
+
216
+ // TODO: Fix unclear naming: Which schema is this, that of the component or of
217
+ // its parent? Isn't exposing `formComponent` and `viewComponent` enough, once
218
+ // we offer access to their components there through `getComponent()` & co. on
219
+ // `DitoMixin` perhaps? Also, there could be a `tabComponent` getter for
220
+ // schemas in tabs?
221
+ get schemaComponent() {
222
+ return get(this, 'schemaComponent', null)
223
+ }
224
+
225
+ get formComponent() {
226
+ return get(this, 'formComponent', null)
227
+ }
228
+
229
+ get viewComponent() {
230
+ return get(this, 'viewComponent', null)
231
+ }
232
+
233
+ get dialogComponent() {
234
+ return get(this, 'dialogComponent', null)
235
+ }
236
+
237
+ get panelComponent() {
238
+ return get(this, 'panelComponent', null)
239
+ }
240
+
241
+ get resourceComponent() {
242
+ return get(this, 'resourceComponent', null)
243
+ }
244
+
245
+ get sourceComponent() {
246
+ return get(this, 'sourceComponent', null)
247
+ }
248
+
249
+ // When used in OptionsMixin for `schema.options.value()`,
250
+ // `schema.options.label()` and `schema.search.filter()` callbacks:
251
+ get option() {
252
+ return get(this, 'option', undefined)
253
+ }
254
+
255
+ get options() {
256
+ return get(this, 'options', undefined)
257
+ }
258
+
259
+ get open() {
260
+ return get(this, 'open', undefined)
261
+ }
262
+
263
+ // For search term in selects:
264
+ get searchTerm() {
265
+ return get(this, 'searchTerm', undefined)
266
+ }
267
+
268
+ // The error field is only populated in the context of buttons that send
269
+ // requests, see `ResourceMixin.emitButtonEvent()`:
270
+ get error() {
271
+ return get(this, 'error', undefined)
272
+ }
273
+
274
+ get wasNotified() {
275
+ return get(this, 'wasNotified', false)
276
+ }
277
+
278
+ get isRunning() {
279
+ return get(this, 'isRunning', false)
280
+ }
281
+
282
+ set isRunning(value) {
283
+ set(this, 'isRunning', value)
284
+ }
285
+
286
+ get query() {
287
+ return this.component.$route.query
288
+ }
289
+
290
+ // Helper Methods
291
+
292
+ get request() {
293
+ return options => this.component.request(options)
294
+ }
295
+
296
+ get format() {
297
+ return (value, options) => this.component.format(value, options)
298
+ }
299
+
300
+ get navigate() {
301
+ return location => this.component.navigate(location)
302
+ }
303
+
304
+ get download() {
305
+ return options => this.component.download(options)
306
+ }
307
+
308
+ get getResourceUrl() {
309
+ return resource => this.component.getResourceUrl(resource)
310
+ }
311
+
312
+ get notify() {
313
+ return options => {
314
+ this.component.notify(options)
315
+ set(this, 'wasNotified', true)
316
+ }
317
+ }
318
+ }
@@ -0,0 +1,42 @@
1
+ // DitoTypeComponent is the abstract base component for all other type
2
+ // components inside the types/ folder. There's also a separate concrete
3
+ // `DitoTypeComponent.vue` component, use to render `{ type: 'component' }`
4
+ import { asArray, camelize } from '@ditojs/utils'
5
+ import DitoComponent from './DitoComponent.js'
6
+ import TypeMixin from './mixins/TypeMixin.js'
7
+ import { registerTypeComponent, getTypeComponent } from './utils/schema.js'
8
+
9
+ // @vue/component
10
+ export default {
11
+ extends: DitoComponent,
12
+ mixins: [TypeMixin],
13
+
14
+ nativeField: false,
15
+ textField: false,
16
+ // Set reasonable defaults for all of these that are used by most type
17
+ // components. These only need defining in sub-classes when they differ.
18
+ defaultValue: null,
19
+ defaultNested: true,
20
+ defaultVisible: true,
21
+ defaultMultiple: false,
22
+ generateLabel: true,
23
+ excludeValue: false,
24
+ ignoreMissingValue: null,
25
+ omitSpacing: false,
26
+
27
+ component: DitoComponent.component,
28
+
29
+ get: getTypeComponent,
30
+
31
+ register(types, definition = {}) {
32
+ types = asArray(types)
33
+ const component = this.component(
34
+ `DitoType${camelize(types[0], true)}`,
35
+ definition
36
+ )
37
+ for (const type of types) {
38
+ registerTypeComponent(type, component)
39
+ }
40
+ return component
41
+ }
42
+ }
@@ -0,0 +1,12 @@
1
+ export default class DitoUser {
2
+ hasRole(...roles) {
3
+ if (this.roles) {
4
+ for (const role of roles) {
5
+ if (this.roles.includes(role)) {
6
+ return true
7
+ }
8
+ }
9
+ }
10
+ return false
11
+ }
12
+ }
@@ -0,0 +1,12 @@
1
+ import { reactive } from 'vue'
2
+ import { parseUserAgent } from './utils/agent'
3
+
4
+ export default reactive({
5
+ title: '',
6
+ routeComponents: [],
7
+ user: null,
8
+ agent: parseUserAgent(navigator.userAgent || ''),
9
+ loadCache: {}, // See TypeMixin.load()
10
+ activeLabel: null,
11
+ clipboardData: null
12
+ })
@@ -0,0 +1,60 @@
1
+ <template lang="pug">
2
+ .dito-account
3
+ a(
4
+ @mousedown.stop="onPulldownMouseDown()"
5
+ )
6
+ span {{ user.username }}
7
+ ul.dito-pulldown(:class="{ 'dito-pulldown--open': pulldown.open }")
8
+ li(
9
+ v-for="(label, value) of items"
10
+ )
11
+ a.dito-pulldown__item(
12
+ @mousedown.stop="onPulldownMouseDown(value)"
13
+ @mouseup="onPulldownMouseUp(value)"
14
+ ) {{ label }}
15
+ </template>
16
+
17
+ <script>
18
+ import DitoComponent from '../DitoComponent.js'
19
+ import PulldownMixin from '../mixins/PulldownMixin.js'
20
+
21
+ // @vue/component
22
+ export default DitoComponent.component('DitoAccount', {
23
+ mixins: [PulldownMixin],
24
+
25
+ data() {
26
+ return {
27
+ items: {
28
+ settings: 'Settings',
29
+ logout: 'Logout'
30
+ }
31
+ }
32
+ },
33
+
34
+ methods: {
35
+ onPulldownSelect(value) {
36
+ switch (value) {
37
+ case 'logout':
38
+ this.rootComponent.logout()
39
+ break
40
+ case 'settings':
41
+ console.info('TODO: Implement Settings')
42
+ break
43
+ }
44
+ }
45
+ }
46
+ })
47
+ </script>
48
+
49
+ <style lang="scss">
50
+ @import '../styles/_imports';
51
+
52
+ .dito-account {
53
+ position: relative;
54
+ display: inline-block;
55
+
56
+ .dito-pulldown {
57
+ top: $pulldown-padding-ver;
58
+ }
59
+ }
60
+ </style>
@@ -0,0 +1,68 @@
1
+ <template lang="pug">
2
+ component(
3
+ v-if="item.type === 'text'"
4
+ :is="item.as || 'div'"
5
+ ) {{ item.text }}
6
+ component(
7
+ v-else-if="item.type === 'html'"
8
+ :is="item.as || 'div'"
9
+ v-html="item.html"
10
+ )
11
+ DitoSpinner(
12
+ v-else-if="item.type === 'spinner'"
13
+ :size="item.size"
14
+ :color="item.color"
15
+ )
16
+ DitoIcon(
17
+ v-else-if="item.type === 'icon'"
18
+ :name="item.name"
19
+ :disabled="item.disabled"
20
+ )
21
+ component(
22
+ v-else-if="item.type === 'component'"
23
+ :is="item.component"
24
+ v-bind="item.props"
25
+ )
26
+ </template>
27
+
28
+ <script>
29
+ import DitoComponent from '../DitoComponent.js'
30
+ import DitoSpinner from './DitoSpinner.vue'
31
+ import { DitoIcon } from '@ditojs/ui/src'
32
+
33
+ export default DitoComponent.component('DitoAffix', {
34
+ components: { DitoSpinner, DitoIcon },
35
+
36
+ props: {
37
+ item: { type: Object, required: true },
38
+ parentContext: { type: Object, required: true }
39
+ },
40
+
41
+ computed: {
42
+ // Override DitoMixin's context with the parent's context
43
+ context() {
44
+ return this.parentContext
45
+ }
46
+ }
47
+ })
48
+ </script>
49
+
50
+ <style lang="scss">
51
+ @import '../styles/_imports';
52
+
53
+ .dito-affix {
54
+ &--html,
55
+ &--text {
56
+ display: inline-block;
57
+ }
58
+
59
+ &--ellipsis {
60
+ @include ellipsis;
61
+
62
+ flex: 1;
63
+ display: block;
64
+ min-width: 0;
65
+ width: 0;
66
+ }
67
+ }
68
+ </style>
@@ -0,0 +1,200 @@
1
+ <template lang="pug">
2
+ .dito-affixes(
3
+ v-if="hasContent"
4
+ :class="classes"
5
+ )
6
+ slot(name="prepend")
7
+ DitoAffix.dito-affix(
8
+ v-for="(item, index) in visibleItems"
9
+ :key="index"
10
+ :class="getItemClasses(item)"
11
+ :style="item.style"
12
+ :item="item"
13
+ :parentContext="parentContext"
14
+ )
15
+ button.dito-affixes__clear(
16
+ v-if="clearable"
17
+ type="button"
18
+ title="Clear"
19
+ :disabled="disabled"
20
+ @click.stop="$emit('clear')"
21
+ @mousedown.stop
22
+ )
23
+ slot(name="append")
24
+ .dito-info(
25
+ v-if="inlineInfo"
26
+ :data-info="inlineInfo"
27
+ )
28
+ </template>
29
+
30
+ <script>
31
+ import DitoComponent from '../DitoComponent.js'
32
+ import DitoAffix from './DitoAffix.vue'
33
+ import { asArray, isString } from '@ditojs/utils'
34
+ import { hasSlotContent } from '@ditojs/ui/src'
35
+ import { shouldRenderSchema } from '../utils/schema.js'
36
+
37
+ export default DitoComponent.component('DitoAffixes', {
38
+ components: { DitoAffix },
39
+ emits: ['clear'],
40
+
41
+ props: {
42
+ items: { type: [String, Object, Array], default: null },
43
+ mode: { type: String, default: null },
44
+ position: { type: String, default: null },
45
+ absolute: { type: Boolean, default: false },
46
+ clearable: { type: Boolean, default: false },
47
+ disabled: { type: Boolean, default: false },
48
+ inlineInfo: { type: String, default: null },
49
+ parentContext: { type: Object, required: true }
50
+ },
51
+
52
+ computed: {
53
+ // Override DitoMixin's context with the parent's context
54
+ context() {
55
+ return this.parentContext
56
+ },
57
+
58
+ processedItems() {
59
+ return asArray(this.items)
60
+ .filter(Boolean)
61
+ .map(item =>
62
+ isString(item)
63
+ ? { type: 'text', text: item }
64
+ : item.type
65
+ ? item
66
+ : item.text != null
67
+ ? { type: 'text', ...item }
68
+ : item.html != null
69
+ ? { type: 'html', ...item }
70
+ : item
71
+ )
72
+ },
73
+
74
+ visibleItems() {
75
+ return this.processedItems.filter(item =>
76
+ shouldRenderSchema(item, this.context)
77
+ )
78
+ },
79
+
80
+ classes() {
81
+ const prefix = 'dito-affixes'
82
+ return {
83
+ [`${prefix}--${this.position}`]: this.position,
84
+ [`${prefix}--${this.mode}`]: this.mode,
85
+ [`${prefix}--absolute`]: this.absolute
86
+ }
87
+ },
88
+
89
+ hasContent() {
90
+ return (
91
+ this.visibleItems.length > 0 ||
92
+ this.clearable ||
93
+ this.inlineInfo ||
94
+ hasSlotContent(this.$slots.prepend) ||
95
+ hasSlotContent(this.$slots.append)
96
+ )
97
+ }
98
+ },
99
+
100
+ methods: {
101
+ getItemClasses(item) {
102
+ const prefix = 'dito-affix'
103
+ return [
104
+ {
105
+ [`${prefix}--${item.type}`]: item.type,
106
+ [`${prefix}--ellipsis`]: (
107
+ this.mode === 'ellipsis' && ['text', 'html'].includes(item.type)
108
+ )
109
+ },
110
+ item.class
111
+ ]
112
+ }
113
+ }
114
+ })
115
+ </script>
116
+
117
+ <style lang="scss">
118
+ @import '../styles/_imports';
119
+
120
+ .dito-affixes {
121
+ $self: &;
122
+
123
+ @include user-select(none);
124
+
125
+ display: flex;
126
+ align-items: center;
127
+ gap: $input-padding-hor;
128
+
129
+ &--absolute {
130
+ position: absolute;
131
+ inset: $border-width;
132
+ margin: $input-padding-ver $input-padding-hor;
133
+ pointer-events: none;
134
+
135
+ > * {
136
+ pointer-events: auto;
137
+ }
138
+
139
+ &#{$self}--prefix {
140
+ right: unset;
141
+ }
142
+
143
+ &#{$self}--suffix {
144
+ left: unset;
145
+ }
146
+ }
147
+
148
+ &--ellipsis {
149
+ flex: 1;
150
+ }
151
+
152
+ &--input {
153
+ color: $color-grey;
154
+
155
+ .dito-icon--disabled {
156
+ color: $color-light;
157
+ }
158
+
159
+ @at-root .dito-component:not(:has(.dito-component)):focus-within & {
160
+ color: $color-active;
161
+ }
162
+ }
163
+
164
+ &__clear {
165
+ --size: #{calc($input-height - 4 * $border-width)};
166
+
167
+ display: none;
168
+ position: relative;
169
+ cursor: pointer;
170
+ width: var(--size);
171
+ height: var(--size);
172
+ border-radius: $border-radius;
173
+ margin: (-$input-padding-ver + $border-width)
174
+ (-$input-padding-hor + $border-width);
175
+ padding: 0;
176
+ border: 0;
177
+
178
+ @at-root .dito-component:not(:has(.dito-component)):hover & {
179
+ display: block;
180
+ }
181
+
182
+ &::before {
183
+ @extend %icon-clear;
184
+
185
+ color: $color-grey;
186
+ }
187
+
188
+ &:hover::before {
189
+ color: $color-black;
190
+ }
191
+ }
192
+
193
+ // Hide other affixes when clear button is shown
194
+ // prettier-ignore
195
+ @at-root .dito-component:hover:not(:has(.dito-component))
196
+ #{$self}:has(#{$self}__clear) > *:not(#{$self}__clear) {
197
+ display: none;
198
+ }
199
+ }
200
+ </style>