@ditojs/admin 2.1.3 → 2.2.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 (38) hide show
  1. package/dist/dito-admin.es.js +1549 -1451
  2. package/dist/dito-admin.umd.js +4 -4
  3. package/dist/style.css +1 -1
  4. package/package.json +5 -5
  5. package/src/DitoContext.js +10 -10
  6. package/src/DitoTypeComponent.js +1 -0
  7. package/src/components/DitoContainer.vue +22 -10
  8. package/src/components/DitoCreateButton.vue +11 -8
  9. package/src/components/DitoDraggable.vue +45 -0
  10. package/src/components/DitoPane.vue +1 -1
  11. package/src/components/DitoPanel.vue +1 -1
  12. package/src/components/DitoSchema.vue +11 -6
  13. package/src/components/DitoSchemaInlined.vue +1 -0
  14. package/src/components/DitoTreeItem.vue +3 -2
  15. package/src/components/index.js +1 -0
  16. package/src/mixins/DataMixin.js +3 -3
  17. package/src/mixins/DitoMixin.js +23 -10
  18. package/src/mixins/RouteMixin.js +1 -1
  19. package/src/mixins/SortableMixin.js +2 -6
  20. package/src/mixins/SourceMixin.js +0 -5
  21. package/src/mixins/TypeMixin.js +40 -17
  22. package/src/styles/_pulldown.scss +9 -12
  23. package/src/types/DitoTypeButton.vue +12 -9
  24. package/src/types/DitoTypeCode.vue +1 -0
  25. package/src/types/DitoTypeComponent.vue +1 -0
  26. package/src/types/DitoTypeLabel.vue +2 -1
  27. package/src/types/DitoTypeList.vue +4 -2
  28. package/src/types/DitoTypeMarkup.vue +2 -0
  29. package/src/types/DitoTypeObject.vue +2 -0
  30. package/src/types/DitoTypePanel.vue +1 -0
  31. package/src/types/DitoTypeSection.vue +1 -1
  32. package/src/types/DitoTypeTextarea.vue +1 -0
  33. package/src/types/DitoTypeTreeList.vue +2 -0
  34. package/src/types/DitoTypeUpload.vue +5 -2
  35. package/src/utils/accessor.js +1 -1
  36. package/src/utils/options.js +1 -0
  37. package/src/utils/schema.js +5 -15
  38. package/types/index.d.ts +3 -24
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ditojs/admin",
3
- "version": "2.1.3",
3
+ "version": "2.2.1",
4
4
  "type": "module",
5
5
  "description": "Dito.js Admin is a schema based admin interface for Dito.js Server, featuring auto-generated views and forms and built with Vue.js",
6
6
  "repository": "https://github.com/ditojs/dito/tree/master/packages/admin",
@@ -33,8 +33,8 @@
33
33
  "not ie_mob > 0"
34
34
  ],
35
35
  "dependencies": {
36
- "@ditojs/ui": "^2.1.2",
37
- "@ditojs/utils": "^2.1.1",
36
+ "@ditojs/ui": "^2.2.0",
37
+ "@ditojs/utils": "^2.2.0",
38
38
  "@kyvg/vue3-notification": "^2.9.0",
39
39
  "@lk77/vue3-color": "^3.0.6",
40
40
  "@tiptap/core": "^2.0.3",
@@ -73,7 +73,7 @@
73
73
  "vue-upload-component": "^3.1.8"
74
74
  },
75
75
  "devDependencies": {
76
- "@ditojs/build": "^2.1.1",
76
+ "@ditojs/build": "^2.2.0",
77
77
  "@vitejs/plugin-vue": "^4.1.0",
78
78
  "@vue/compiler-sfc": "^3.2.47",
79
79
  "pug": "^3.0.2",
@@ -82,7 +82,7 @@
82
82
  "vite": "^4.2.1"
83
83
  },
84
84
  "types": "types",
85
- "gitHead": "39cbb715f40b8c3c657ca8d6790def22f170b31b",
85
+ "gitHead": "306d4d9ef2a0c989b026f1d1cd77f6d4fd60430b",
86
86
  "scripts": {
87
87
  "build": "vite build",
88
88
  "watch": "yarn build --mode 'development' --watch",
@@ -103,27 +103,27 @@ export default class DitoContext {
103
103
  return getLastDataPathIndex(this.parentItemDataPath)
104
104
  }
105
105
 
106
+ // NOTE: While internally, we speak of `data`, in the API surface the
107
+ // term `item` is used for the data that relates to editing objects:
108
+ // If `data` isn't provided, we can determine it from rootData & dataPath:
106
109
  get item() {
107
- // NOTE: While internally, we speak of `data`, in the API surface the
108
- // term `item` is used for the data that relates to editing objects:
109
- // If `data` isn't provided, we can determine it from rootData & dataPath:
110
110
  return get(this, 'data', () =>
111
111
  getItem(this.rootItem, this.dataPath, this.nested)
112
112
  )
113
113
  }
114
114
 
115
115
  // NOTE: `parentItem` isn't the closest data parent to `item`, it's the
116
- // closest parent that isn't an array, e.g. for relations or nested JSON
117
- // data. This is why the term `item` was chosen over `data`, e.g. VS the
118
- // use of `parentData` in server-sided validation, which is the closest
119
- // parent. If needed, we could expose this data here too, as we can do all
120
- // sorts of data processing with `rootData` and `dataPath`.
116
+ // closest parent that isn't an array, e.g. for relations or nested JSON data.
117
+ // This is why the term `item` was chosen over `data`, e.g. VS the use of
118
+ // `parentData` in server-sided validation, which is the closest parent. If
119
+ // needed, we could expose this data here too, as we can do all sorts of data
120
+ // processing with `rootData` and `dataPath`.
121
121
  get parentItem() {
122
- const parentItem = (
122
+ const item = (
123
123
  getParentItem(this.rootItem, this.dataPath, this.nested) ||
124
124
  null
125
125
  )
126
- return parentItem !== this.item ? parentItem : null
126
+ return item !== this.item ? item : null
127
127
  }
128
128
 
129
129
  get rootItem() {
@@ -21,6 +21,7 @@ export default {
21
21
  generateLabel: true,
22
22
  excludeValue: false,
23
23
  ignoreMissingValue: null,
24
+ alignBottom: true,
24
25
  omitPadding: false,
25
26
 
26
27
  component: DitoComponent.component,
@@ -5,7 +5,7 @@
5
5
  :style="containerStyle"
6
6
  )
7
7
  DitoLabel(
8
- v-if="label"
8
+ v-if="hasLabel"
9
9
  :label="label"
10
10
  :dataPath="labelDataPath"
11
11
  :class="componentClass"
@@ -31,7 +31,7 @@ import { isString, isNumber } from '@ditojs/utils'
31
31
  import DitoComponent from '../DitoComponent.js'
32
32
  import DitoContext from '../DitoContext.js'
33
33
  import { getSchemaAccessor } from '../utils/accessor.js'
34
- import { getTypeComponent, shouldOmitPadding } from '../utils/schema.js'
34
+ import { getTypeComponent, alignBottom, omitPadding } from '../utils/schema.js'
35
35
  import { parseFraction } from '../utils/math.js'
36
36
 
37
37
  // @vue/component
@@ -64,11 +64,12 @@ export default DitoComponent.component('DitoContainer', {
64
64
  },
65
65
 
66
66
  hasLabel() {
67
- const { schema } = this
68
- const { label } = schema
67
+ const { label } = this.schema
69
68
  return (
70
- label !== false &&
71
- (!!label || this.typeComponent?.generateLabel && this.generateLabels)
69
+ label !== false && (
70
+ !!label ||
71
+ this.generateLabels && this.typeComponent?.generateLabel
72
+ )
72
73
  )
73
74
  },
74
75
 
@@ -122,9 +123,12 @@ export default DitoComponent.component('DitoContainer', {
122
123
 
123
124
  containerClass() {
124
125
  const { class: containerClass } = this.schema
126
+ const prefix = 'dito-container'
125
127
  return {
126
- 'dito-single': this.single,
127
- 'dito-omit-padding': shouldOmitPadding(this.schema),
128
+ [`${prefix}--single`]: this.single,
129
+ [`${prefix}--has-label`]: this.hasLabel,
130
+ [`${prefix}--align-bottom`]: alignBottom(this.schema),
131
+ [`${prefix}--omit-padding`]: omitPadding(this.schema),
128
132
  ...(
129
133
  isString(containerClass)
130
134
  ? { [containerClass]: true }
@@ -162,6 +166,7 @@ export default DitoComponent.component('DitoContainer', {
162
166
  componentClass() {
163
167
  const basisIsAuto = this.componentBasis === 'auto'
164
168
  return {
169
+ // TODO: BEM
165
170
  'dito-single': this.single,
166
171
  'dito-disabled': this.componentDisabled,
167
172
  'dito-width-fill': !basisIsAuto || this.componentWidth === 'fill',
@@ -183,6 +188,9 @@ export default DitoComponent.component('DitoContainer', {
183
188
  @import '../styles/_imports';
184
189
 
185
190
  .dito-container {
191
+ display: flex;
192
+ flex-flow: column;
193
+ align-items: flex-start;
186
194
  // Needed for better vertical alignment:
187
195
  align-self: stretch;
188
196
  box-sizing: border-box;
@@ -196,7 +204,11 @@ export default DitoComponent.component('DitoContainer', {
196
204
  padding: 0;
197
205
  }
198
206
 
199
- &.dito-omit-padding {
207
+ &--align-bottom {
208
+ justify-content: end; // To align components with and without labels.
209
+ }
210
+
211
+ &--omit-padding {
200
212
  padding: 0;
201
213
 
202
214
  > .dito-label {
@@ -204,7 +216,7 @@ export default DitoComponent.component('DitoContainer', {
204
216
  }
205
217
  }
206
218
 
207
- &.dito-single {
219
+ &--single {
208
220
  height: 100%; // So that list buttons can be sticky at the bottom;
209
221
  }
210
222
  }
@@ -14,8 +14,9 @@
14
14
  v-for="(form, type) in forms"
15
15
  )
16
16
  a(
17
- v-if="isCreatable(form)"
18
- :class="`dito-type-${type}`"
17
+ v-if="shouldRender(form)"
18
+ v-show="shouldShow(form)"
19
+ :class="getFormClass(form, type)"
19
20
  @mousedown.stop="onPulldownMouseDown(type)"
20
21
  @mouseup="onPulldownMouseUp(type)"
21
22
  ) {{ getLabel(form) }}
@@ -59,13 +60,8 @@ export default DitoComponent.component('DitoCreateButton', {
59
60
  },
60
61
 
61
62
  methods: {
62
- isCreatable(form) {
63
- // Forms can be excluded from the list by providing `creatable: false`
64
- return form.creatable !== false
65
- },
66
-
67
63
  createItem(form, type = null) {
68
- if (this.isCreatable(form)) {
64
+ if (this.shouldRender(form) && !this.shouldDisable(form)) {
69
65
  if (this.isInlined) {
70
66
  this.sourceComponent.createItem(form, type)
71
67
  } else {
@@ -79,6 +75,13 @@ export default DitoComponent.component('DitoCreateButton', {
79
75
  }
80
76
  },
81
77
 
78
+ getFormClass(form, type) {
79
+ return {
80
+ [`dito-type-${type}`]: true,
81
+ 'dito-disabled': this.shouldDisable(form)
82
+ }
83
+ },
84
+
82
85
  onPulldownSelect(type) {
83
86
  this.createItem(this.forms[type], type)
84
87
  this.setPulldownOpen(false)
@@ -0,0 +1,45 @@
1
+ <template lang="pug">
2
+ UseSortable(
3
+ v-if="draggable"
4
+ :tag="tag"
5
+ :modelValue="modelValue"
6
+ :options="options"
7
+ @update:modelValue="$emit('update:modelValue', $event)"
8
+ )
9
+ slot
10
+ component(
11
+ v-else
12
+ :is="tag"
13
+ )
14
+ slot
15
+ </template>
16
+
17
+ <script>
18
+ import DitoComponent from '../DitoComponent'
19
+ import { UseSortable } from '@vueuse/integrations/useSortable/component'
20
+
21
+ // @vue/component
22
+ export default DitoComponent.component('DitoDraggable', {
23
+ components: { UseSortable },
24
+ emits: ['update:modelValue'],
25
+
26
+ props: {
27
+ modelValue: {
28
+ type: Array,
29
+ required: true
30
+ },
31
+ tag: {
32
+ type: String,
33
+ default: 'div'
34
+ },
35
+ options: {
36
+ type: Object,
37
+ required: true
38
+ },
39
+ draggable: {
40
+ type: Boolean,
41
+ default: true
42
+ }
43
+ }
44
+ })
45
+ </script>
@@ -162,7 +162,7 @@ export default DitoComponent.component('DitoPane', {
162
162
  margin-top: 0;
163
163
  }
164
164
 
165
- .dito-container.dito-omit-padding > & {
165
+ .dito-container--omit-padding > & {
166
166
  // Clear margins set above again if parent is omitting padding.
167
167
  margin: 0;
168
168
  max-width: unset;
@@ -121,7 +121,7 @@ export default DitoComponent.component('DitoPanel', {
121
121
  const { data } = this.schema
122
122
  if (data) {
123
123
  this.ownData = isFunction(data)
124
- ? data.call(this, this.context)
124
+ ? data(this.context)
125
125
  : data
126
126
  }
127
127
  },
@@ -142,7 +142,7 @@ export default DitoComponent.component('DitoSchema', {
142
142
  // Allow schema to provide more data through `schema.data`, vue-style:
143
143
  ...(
144
144
  data && isFunction(data)
145
- ? data.call(this, this.context)
145
+ ? data(this.context)
146
146
  : data
147
147
  ),
148
148
  componentsRegistry: {},
@@ -197,7 +197,7 @@ export default DitoComponent.component('DitoSchema', {
197
197
  const { defaultTab } = tab
198
198
  if (
199
199
  isFunction(defaultTab)
200
- ? defaultTab.call(this, this.context)
200
+ ? defaultTab(this.context)
201
201
  : defaultTab
202
202
  ) {
203
203
  return tab
@@ -214,6 +214,11 @@ export default DitoComponent.component('DitoSchema', {
214
214
  return this.schema?.clipboard
215
215
  },
216
216
 
217
+ parentData() {
218
+ const data = getParentItem(this.rootData, this.dataPath, false)
219
+ return data !== this.data ? data : null
220
+ },
221
+
217
222
  processedData() {
218
223
  return this.processData({ target: 'server', schemaOnly: true })
219
224
  },
@@ -235,6 +240,10 @@ export default DitoComponent.component('DitoSchema', {
235
240
  return this.data
236
241
  },
237
242
 
243
+ parentItem() {
244
+ return this.parentData
245
+ },
246
+
238
247
  rootItem() {
239
248
  return this.rootData
240
249
  },
@@ -247,10 +256,6 @@ export default DitoComponent.component('DitoSchema', {
247
256
  return this.clipboardData
248
257
  },
249
258
 
250
- parentItem() {
251
- return getParentItem(this.rootData, this.dataPath, false)
252
- },
253
-
254
259
  formLabel() {
255
260
  return this.getLabel(
256
261
  this.getItemFormSchema(this.sourceSchema, this.data, this.context)
@@ -10,6 +10,7 @@ DitoSchema.dito-schema-inlined(
10
10
  :disabled="disabled"
11
11
  :collapsed="collapsed"
12
12
  :collapsible="collapsible"
13
+ :generateLabels="!isCompact"
13
14
  :class="{ 'dito-schema-compact': isCompact }"
14
15
  )
15
16
  //- Render dito-edit-buttons for inlined schemas separately from all
@@ -72,11 +72,12 @@
72
72
  :store="store"
73
73
  :disabled="disabled"
74
74
  )
75
- UseSortable(
75
+ DitoDraggable(
76
76
  v-if="childrenSchema"
77
77
  v-show="opened"
78
78
  :modelValue="updateOrder(childrenSchema, childrenList)"
79
- :options="getSortableOptions(childrenDraggable, true)"
79
+ :options="getSortableOptions(true)"
80
+ :draggable="childrenDraggable"
80
81
  @update:modelValue="value => (childrenList = value)"
81
82
  )
82
83
  DitoTreeItem(
@@ -30,4 +30,5 @@ export { default as DitoPagination } from './DitoPagination.vue'
30
30
  export { default as DitoTreeItem } from './DitoTreeItem.vue'
31
31
  export { default as DitoTableHead } from './DitoTableHead.vue'
32
32
  export { default as DitoTableCell } from './DitoTableCell.vue'
33
+ export { default as DitoDraggable } from './DitoDraggable.vue'
33
34
  export { default as DitoVNode } from './DitoVNode.vue'
@@ -38,7 +38,7 @@ export default {
38
38
  // If the data callback provided a dependency function when it was called,
39
39
  // cal it in every call of `handleDataSchema()` to force Vue to keep track
40
40
  // of the async dependencies.
41
- asyncEntry.dependencyFunction?.call(this, this.context)
41
+ asyncEntry.dependencyFunction?.(this.context)
42
42
 
43
43
  if (asyncEntry.resolveCounter > 0) {
44
44
  // If the data was resolved already, return it and clear the value once
@@ -59,7 +59,7 @@ export default {
59
59
  data = null
60
60
  } else if (data) {
61
61
  if (isFunction(data)) {
62
- const result = data.call(this, this.context)
62
+ const result = data(this.context)
63
63
  // If the result of the data function is another function, then the
64
64
  // first data function is there to track dependencies and the real
65
65
  // data loading happens in the function that it returned. Keep track
@@ -68,7 +68,7 @@ export default {
68
68
  // the function that it returned once to get the actual data:
69
69
  if (isFunction(result)) {
70
70
  asyncEntry.dependencyFunction = data
71
- data = result.call(this, this.context)
71
+ data = result(this.context)
72
72
  } else {
73
73
  data = result
74
74
  }
@@ -213,7 +213,7 @@ export default {
213
213
  if (value === undefined && def !== undefined) {
214
214
  if (callback && isFunction(def) && !isMatchingType(types, def)) {
215
215
  // Support `default()` functions for any type except `Function`:
216
- def = def.call(this, this.context)
216
+ def = def.call(this)
217
217
  }
218
218
  return def
219
219
  }
@@ -224,7 +224,7 @@ export default {
224
224
  // Any schema value handled through `getSchemaValue()` can provide
225
225
  // a function that's resolved when the value is evaluated:
226
226
  if (callback && isFunction(value)) {
227
- value = value.call(this, this.context)
227
+ value = value(this.context)
228
228
  }
229
229
  // Now finally see if we can convert to the expect types.
230
230
  if (types && value != null && !isMatchingType(types, value)) {
@@ -265,14 +265,27 @@ export default {
265
265
  },
266
266
 
267
267
  shouldRender(schema = null) {
268
- return (
269
- !!schema &&
270
- this.getSchemaValue('if', {
271
- type: Boolean,
272
- default: true,
273
- schema
274
- })
275
- )
268
+ return this.getSchemaValue('if', {
269
+ type: Boolean,
270
+ default: true,
271
+ schema
272
+ })
273
+ },
274
+
275
+ shouldShow(schema = null) {
276
+ return this.getSchemaValue('visible', {
277
+ type: Boolean,
278
+ default: true,
279
+ schema
280
+ })
281
+ },
282
+
283
+ shouldDisable(schema = null) {
284
+ return this.getSchemaValue('disabled', {
285
+ type: Boolean,
286
+ default: false,
287
+ schema
288
+ })
276
289
  },
277
290
 
278
291
  getResourcePath(resource) {
@@ -69,7 +69,7 @@ export default {
69
69
  },
70
70
 
71
71
  meta() {
72
- return this.routeRecord.meta
72
+ return this.routeRecord?.meta
73
73
  },
74
74
 
75
75
  path() {
@@ -1,10 +1,8 @@
1
1
  import ItemMixin from './ItemMixin.js'
2
- import { UseSortable } from '@vueuse/integrations/useSortable/component'
3
2
 
4
3
  // @vue/component
5
4
  export default {
6
5
  mixins: [ItemMixin],
7
- components: { UseSortable },
8
6
 
9
7
  data() {
10
8
  return {
@@ -13,17 +11,15 @@ export default {
13
11
  },
14
12
 
15
13
  methods: {
16
- getSortableOptions(draggable, fallback = false) {
14
+ getSortableOptions(forceFallback = false) {
17
15
  return {
18
16
  animation: 150,
19
- // TODO: This is broken in VueSortable, always enable it for now.
20
- // disabled: !draggable,
21
17
  handle: '.dito-button-drag',
22
18
  dragClass: 'dito-sortable-active',
23
19
  chosenClass: 'dito-sortable-chosen',
24
20
  ghostClass: 'dito-sortable-ghost',
25
21
  fallbackClass: 'dito-sortable-fallback',
26
- forceFallback: fallback,
22
+ forceFallback,
27
23
  onStart: this.onStartDrag,
28
24
  onEnd: this.onEndDrag
29
25
  }
@@ -11,7 +11,6 @@ import {
11
11
  hasFormSchema,
12
12
  getFormSchemas,
13
13
  getViewSchema,
14
- hasLabels,
15
14
  isCompact,
16
15
  isInlined,
17
16
  isObjectSource,
@@ -249,10 +248,6 @@ export default {
249
248
  return getButtonSchemas(this.schema.buttons)
250
249
  },
251
250
 
252
- hasLabels() {
253
- return this.forms.some(hasLabels)
254
- },
255
-
256
251
  isCompact() {
257
252
  return this.forms.every(isCompact)
258
253
  },
@@ -3,7 +3,7 @@ import ValidationMixin from './ValidationMixin.js'
3
3
  import { getSchemaAccessor } from '../utils/accessor.js'
4
4
  import { computeValue } from '../utils/schema.js'
5
5
  import { getItem, getParentItem } from '../utils/data.js'
6
- import { isString, asArray } from '@ditojs/utils'
6
+ import { isString, asArray, camelize } from '@ditojs/utils'
7
7
 
8
8
  // @vue/component
9
9
  export default {
@@ -71,14 +71,32 @@ export default {
71
71
  }
72
72
  },
73
73
 
74
+ parentData() {
75
+ const data = getParentItem(this.rootData, this.dataPath, this.nested)
76
+ return data !== this.data ? data : null
77
+ },
78
+
79
+ processedData() {
80
+ // We can only get the processed data through the schemaComponent, but
81
+ // that's not necessarily the item represented by this component.
82
+ // Solution: Find the relative path and the processed sub-item from there:
83
+ const { schemaComponent } = this
84
+ return getItem(
85
+ schemaComponent.processedData,
86
+ // Get the dataPath relative to the schemaComponent's data:
87
+ this.dataPath.slice(schemaComponent.dataPath.length),
88
+ this.nested
89
+ )
90
+ },
91
+
74
92
  // The following computed properties are similar to `DitoContext`
75
93
  // properties, so that we can access these on `this` as well:
76
94
  item() {
77
- return getItem(this.rootItem, this.dataPath, this.nested)
95
+ return this.data
78
96
  },
79
97
 
80
98
  parentItem() {
81
- return getParentItem(this.rootItem, this.dataPath, this.nested)
99
+ return this.parentData
82
100
  },
83
101
 
84
102
  rootItem() {
@@ -86,16 +104,7 @@ export default {
86
104
  },
87
105
 
88
106
  processedItem() {
89
- // We can only get the processed items through the schemaComponent, but
90
- // that's not necessarily the item represented by this component.
91
- // Solution: Find the relative path and the processed sub-item from there:
92
- const { schemaComponent } = this
93
- return getItem(
94
- schemaComponent.processedItem,
95
- // Get the dataPath relative to the schemaComponent's data:
96
- this.dataPath.slice(schemaComponent.dataPath.length),
97
- this.nested
98
- )
107
+ return this.processedData
99
108
  },
100
109
 
101
110
  label: getSchemaAccessor('label', {
@@ -120,7 +129,7 @@ export default {
120
129
  }
121
130
  }),
122
131
 
123
- // TODO: Rename to `excluded` for consistent naming
132
+ // TODO: Rename to `excluded` for consistent naming?
124
133
  exclude: getSchemaAccessor('exclude', {
125
134
  type: Boolean,
126
135
  default: false
@@ -156,10 +165,18 @@ export default {
156
165
  type: String
157
166
  }),
158
167
 
159
- // @overridable
160
168
  events() {
161
- const { onFocus, onBlur, onInput, onChange } = this
162
- return { onFocus, onBlur, onInput, onChange }
169
+ const events = this.getEvents()
170
+ // Register callbacks for all provides non-recognized events,
171
+ // assuming they are native events.
172
+ // TODO: Move to vue3-style `on[A-Z]` event handlers naming that aren't
173
+ // namespaced in `schema.events` once the transition is complete.
174
+ for (const event of Object.keys(this.schema.events || {})) {
175
+ events[`on${camelize(event, true)}`] ??= () => {
176
+ this.emitEvent(event)
177
+ }
178
+ }
179
+ return events
163
180
  },
164
181
 
165
182
  attributes() {
@@ -224,6 +241,12 @@ export default {
224
241
  }
225
242
  },
226
243
 
244
+ // @overridable
245
+ getEvents() {
246
+ const { onFocus, onBlur, onInput, onChange } = this
247
+ return { onFocus, onBlur, onInput, onChange }
248
+ },
249
+
227
250
  // @overridable
228
251
  getValidations() {
229
252
  return null
@@ -1,3 +1,5 @@
1
+ @import '../styles/_imports';
2
+
1
3
  ul.dito-pulldown {
2
4
  display: none;
3
5
  position: absolute;
@@ -5,6 +7,7 @@ ul.dito-pulldown {
5
7
  z-index: 1000;
6
8
  border-radius: $pulldown-radius;
7
9
  box-shadow: $shadow-window;
10
+ overflow: hidden;
8
11
 
9
12
  &.dito-open {
10
13
  display: block;
@@ -15,25 +18,19 @@ ul.dito-pulldown {
15
18
 
16
19
  a {
17
20
  display: block;
18
- text-align: center;
19
21
  padding: $pulldown-padding;
20
22
  line-height: 1;
21
23
  background: $button-color;
22
24
 
23
- &:hover {
25
+ &.dito-disabled {
26
+ color: $color-disabled;
27
+ cursor: default;
28
+ }
29
+
30
+ &:not(.dito-disabled):hover {
24
31
  background: $color-active;
25
32
  color: $color-white;
26
33
  }
27
34
  }
28
-
29
- &:first-child a {
30
- border-top-left-radius: $pulldown-radius;
31
- border-top-right-radius: $pulldown-radius;
32
- }
33
-
34
- &:last-child a {
35
- border-bottom-left-radius: $pulldown-radius;
36
- border-bottom-right-radius: $pulldown-radius;
37
- }
38
35
  }
39
36
  }