wave-ui 1.62.0 → 1.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-ui",
3
- "version": "1.62.0",
3
+ "version": "1.63.0",
4
4
  "description": "An emerging UI framework for Vue.js (2 & 3) with only the bright side. :sunny:",
5
5
  "author": "Antoni Andre <antoniandre.web@gmail.com>",
6
6
  "homepage": "https://antoniandre.github.io/wave-ui",
@@ -28,6 +28,7 @@ export { default as WProgress } from './w-progress.vue'
28
28
  export { default as WRadio } from './w-radio.vue'
29
29
  export { default as WRadios } from './w-radios.vue'
30
30
  export { default as WRating } from './w-rating.vue'
31
+ export { default as WScrollbar } from './w-scrollbar.vue'
31
32
  export { default as WSelect } from './w-select.vue'
32
33
  export { default as WSlider } from './w-slider.vue'
33
34
  export { default as WSpinner } from './w-spinner.vue'
@@ -407,7 +407,10 @@ export default {
407
407
  if (!this.isMultipleSelect) this.listItems.forEach(item => (item._selected = false))
408
408
 
409
409
  this.checkSelection(selection) // Create an array with the selected values.
410
- .forEach(val => (this.listItems.find(item => item._value === val)._selected = true))
410
+ .forEach(val => {
411
+ const foundItem = this.listItems.find(item => item._value === val)
412
+ if (foundItem) foundItem._selected = true
413
+ })
411
414
  }
412
415
  },
413
416
 
@@ -63,12 +63,12 @@ export default {
63
63
  .w-notification-manager {
64
64
  position: fixed;
65
65
  top: 0;
66
- bottom: 0;
66
+ max-height: 100%;
67
67
  right: 0;
68
68
  z-index: 1000;
69
- pointer-events: none;
70
69
  width: 280px;
71
70
  overflow-x: hidden;
71
+ overflow-y: auto;
72
72
 
73
73
  &--left {right: auto;left: 0;}
74
74
 
@@ -79,7 +79,6 @@ export default {
79
79
  right: 0;
80
80
  margin: 8px;
81
81
  flex-grow: 1;
82
- pointer-events: all;
83
82
  }
84
83
  }
85
84
  </style>
@@ -0,0 +1,24 @@
1
+ <template lang="pug">
2
+ .w-scrollbar
3
+ </template>
4
+
5
+ <script>
6
+ export default {
7
+ name: 'w-scrollbar',
8
+ props: {
9
+
10
+ },
11
+
12
+ emits: [],
13
+
14
+ data: () => ({
15
+
16
+ })
17
+ }
18
+ </script>
19
+
20
+ <style lang="scss">
21
+ .w-scrollbar {
22
+
23
+ }
24
+ </style>
@@ -525,7 +525,7 @@ export default {
525
525
  top: 50%;
526
526
  left: 0;
527
527
  right: 0;
528
- // Use margin instead of padding as the scale transformation bellow decreases the real padding
528
+ // Use margin instead of padding as the scale transformation below decreases the real padding
529
529
  // size and misaligns the label.
530
530
  margin-left: 2 * $base-increment;
531
531
  transform: translateY(-50%);
@@ -820,10 +820,18 @@ $tr-border-top: 1px;
820
820
 
821
821
  // Mobile layout.
822
822
  .w-table--mobile {
823
+ display: flex;
824
+
823
825
  thead {display: none;}
824
- td {display: block;}
826
+ tbody {
827
+ display: flex;
828
+ flex-direction: column;
829
+ flex-grow: 1;
830
+ }
825
831
  tr {
826
- display: block;
832
+ display: flex;
833
+ flex-direction: column;
834
+ flex-grow: 1;
827
835
  padding-top: $base-increment;
828
836
  padding-bottom: $base-increment;
829
837
  }
@@ -4,22 +4,27 @@ ul.w-tree(:class="classes")
4
4
  v-for="(item, i) in currentDepthItems"
5
5
  :key="i"
6
6
  :class="itemClasses(item)")
7
- .w-tree__item-label(
8
- @click="!disabled && onLabelClick(item, $event)"
9
- @keydown="!disabled && onLabelKeydown(item, $event)"
10
- :tabindex="!disabled && (item.children || item.branch || selectable) && !(unexpandableEmpty && !item.children) ? 0 : null")
7
+ //- The keys `route` & `disabled` are always present in any currentDepthItems.
8
+ component.w-tree__item-label(
9
+ :is="!disabled && !item.disabled && item.route ? (!$router || hasExternalLink(item) ? 'a' : 'router-link') : 'div'"
10
+ v-bind="item.route && { [!$router || hasExternalLink(item) ? 'href' : 'to']: item.route }"
11
+ @click="!disabled && !item.disabled && onLabelClick(item, $event)"
12
+ @keydown="!disabled && !item.disabled && onLabelKeydown(item, $event)"
13
+ :tabindex="!disabled && !item.disabled && (item.children || item.branch || selectable) && !(unexpandableEmpty && !item.children) ? 0 : null")
14
+ //- @click.stop to not follow link if item is a link.
11
15
  w-button.w-tree__item-expand(
12
16
  v-if="(item.children || item.branch) && ((expandOpenIcon && item.open) || expandIcon) && !(unexpandableEmpty && !item.children)"
17
+ @click.stop="!disabled && !item.disabled && onLabelClick(item, $event)"
13
18
  color="inherit"
14
19
  :icon="(item.open && expandOpenIcon) || expandIcon"
15
20
  :icon-props="{ rotate90a: !item.open }"
16
21
  :tabindex="-1"
17
- :disabled="disabled"
22
+ :disabled="disabled || item.disabled"
18
23
  text
19
24
  sm)
20
- slot(name="item-label" :item="item.originalItem" :depth="depth" :open="item.open")
25
+ slot(name="item" :item="item.originalItem" :depth="depth" :open="item.open")
21
26
  w-icon(v-if="itemIcon(item)" class="w-tree__item-icon" :color="item.originalItem[itemIconColorKey] || iconColor") {{ itemIcon(item) }}
22
- span {{ item.label }}
27
+ span(v-html="item.label")
23
28
  span.ml1(v-if="counts && (item.children || item.branch)").
24
29
  ({{ item.originalItem.children ? item.originalItem.children.length : 0 }})
25
30
  component(
@@ -39,16 +44,15 @@ ul.w-tree(:class="classes")
39
44
  @click="$emit('click', $event)"
40
45
  @select="$emit('select', $event)"
41
46
  @input="$emit('input', $event)")
42
- template(#item-label="{ item, depth, open }")
43
- slot(name="item-label" :item="item" :depth="depth" :open="open")
47
+ template(#item="{ item, depth, open }")
48
+ slot(name="item" :item="item" :depth="depth" :open="open")
44
49
  </template>
45
50
 
46
51
  <script>
52
+ import { consoleWarn } from '../utils/console'
47
53
  /**
48
- * @todo things to support:
49
- * - items routes
50
- * - icon per item
51
- * - left border?
54
+ * @todo:
55
+ * - option to add a left border.
52
56
  **/
53
57
 
54
58
  export default {
@@ -74,7 +78,10 @@ export default {
74
78
  counts: { type: Boolean },
75
79
  itemIconKey: { type: String, default: 'icon' }, // Support a different icon per item.
76
80
  iconColor: { type: String }, // Applies a color on all the label item icons.
77
- itemIconColorKey: { type: String, default: 'iconColor' } // Applies a specific color on each label item icons.
81
+ itemIconColorKey: { type: String, default: 'iconColor' }, // Applies a specific color on each label item icons.
82
+ itemRouteKey: { type: String, default: 'route' }, // Uses a router link if the item has the `route` key.
83
+ itemDisabledKey: { type: String, default: 'disabled' }, // Disables the item click and selection.
84
+ itemOpenKey: { type: String, default: 'open' } // Open the item by default.
78
85
  },
79
86
 
80
87
  emits: ['input', 'before-open', 'open', 'before-close', 'close', 'click', 'select'],
@@ -100,6 +107,12 @@ export default {
100
107
  updateCurrentDepthTree (items, oldItems = []) {
101
108
  this.currentDepthItems = []
102
109
 
110
+ if (!Array.isArray(items) && typeof items !== 'object') {
111
+ return consoleWarn(`[w-tree] the tree items must be of type array or object, ${typeof items} received.`, items)
112
+ }
113
+
114
+ if (!Array.isArray(items)) items = [items]
115
+
103
116
  items.forEach((item, i) => {
104
117
  this.currentDepthItems.push({
105
118
  originalItem: item, // Store the original item to return it on event emits.
@@ -107,8 +120,10 @@ export default {
107
120
  label: item.label,
108
121
  children: !!item.children, // The children tree remains available in originalItem.
109
122
  branch: item.branch,
123
+ route: item[this.itemRouteKey],
124
+ disabled: item[this.itemDisabledKey],
110
125
  depth: this.depth,
111
- open: !!(oldItems[i]?.open || this.expandAll)
126
+ open: !!(oldItems[i]?.open || this.expandAll || item[this.itemOpenKey])
112
127
  })
113
128
  })
114
129
  },
@@ -135,6 +150,9 @@ export default {
135
150
  },
136
151
 
137
152
  onLabelClick (item, e) {
153
+ const route = item[this.itemRouteKey]
154
+ if (route && this.$router && !this.hasExternalLink(item)) e.preventDefault()
155
+
138
156
  this.$emit('click', { item: item.originalItem, depth: this.depth, e })
139
157
  if (item.children || (item.branch && !this.unexpandableEmpty)) this.expandDepth(item)
140
158
 
@@ -219,9 +237,14 @@ export default {
219
237
  )
220
238
  },
221
239
 
240
+ hasExternalLink (item) {
241
+ return /^(https?:)?\/\/|mailto:|tel:/.test(item[this.itemRouteKey])
242
+ },
243
+
222
244
  itemClasses (item) {
223
245
  return {
224
246
  [item.children || item.branch ? 'w-tree__item--branch' : 'w-tree__item--leaf']: true,
247
+ 'w-tree__item--disabled': item[this.itemDisabledKey],
225
248
  'w-tree__item--empty': item.branch && !item.children,
226
249
  'w-tree__item--unexpandable': item.branch && !item.children && this.unexpandableEmpty
227
250
  }
@@ -264,6 +287,7 @@ $expand-icon-size: 20px;
264
287
  position: relative;
265
288
  display: inline-flex;
266
289
  align-items: center;
290
+ user-select: none;
267
291
 
268
292
  &:before {
269
293
  content: '';
@@ -274,16 +298,24 @@ $expand-icon-size: 20px;
274
298
  right: - $base-increment - 2px;
275
299
  border-radius: $border-radius;
276
300
  }
301
+ &:hover:before {background-color: rgba($primary, 0.05);}
277
302
  &:focus:before {background-color: rgba($primary, 0.1);}
278
303
  }
279
304
  &__item--leaf &__item-label:before {
280
305
  left: - $base-increment;
281
306
  right: - $base-increment;
282
307
  }
308
+ &__item--disabled &__item-label {opacity: 0.5;}
309
+ &__item--disabled &__item-label:before {display: none;}
283
310
 
284
311
  &__item-expand {margin-right: 2px;}
285
312
 
286
313
  &__item--branch > &__item-label {cursor: pointer;}
314
+ &__item--disabled > &__item-label {
315
+ color: $disabled-color;
316
+ cursor: not-allowed;
317
+ -webkit-tap-highlight-color: transparent;
318
+ }
287
319
  &__item--unexpandable > &__item-label {
288
320
  margin-left: $expand-icon-size + 2px;
289
321
  cursor: auto;
@@ -1,40 +1,45 @@
1
- import config, { mergeConfig } from './utils/config'
1
+ import { mergeConfig } from './utils/config'
2
2
  import NotificationManager from './utils/notification-manager'
3
- import colors from './utils/colors'
3
+ import { colorPalette, generateColorShades, flattenColors } from './utils/colors'
4
4
  // import * as directives from './directives'
5
5
 
6
- const shadeColor = (color, amount) => {
7
- return '#' + color.slice(1).match(/../g)
8
- .map(x => (x =+ `0x${x}` + amount, x < 0 ? 0 : ( x > 255 ? 255 : x)).toString(16).padStart(2, 0))
9
- .join('')
6
+ /**
7
+ * Inject presets into a Vue component props defaults before its registration into the app.
8
+ *
9
+ * @param {Object} component the Vue component to inject presets into.
10
+ * @param {Object} presets the presets to inject. E.g. `{ bgColor: 'green' }`.
11
+ */
12
+ const injectPresets = (component, presets) => {
13
+ for (const preset in presets) {
14
+ component.props[preset].default = presets[preset]
15
+ }
10
16
  }
11
17
 
12
18
  export default class WaveUI {
13
19
  static instance = null
14
20
  static vueInstance = null // Needed until constructor is called.
15
- #notificationManager
16
21
 
17
- // Public breakpoint object. Accessible from this.$waveui.breakpoint.
18
- breakpoint = {
19
- name: '',
20
- xs: false,
21
- sm: false,
22
- md: false,
23
- lg: false,
24
- xl: false
22
+ // Exposed as a global object and also `app.provide`d.
23
+ // Accessible from this.$waveui, or inject('$waveui').
24
+ $waveui = {
25
+ breakpoint: {
26
+ name: '',
27
+ xs: false,
28
+ sm: false,
29
+ md: false,
30
+ lg: false,
31
+ xl: false
32
+ },
33
+ config: {},
34
+ colors: {}, // Object of pairs of color-name => color hex.
35
+ _notificationManager: null,
36
+
37
+ // Callable from this.$waveui.
38
+ notify (...args) {
39
+ this._notificationManager.notify(...args)
40
+ }
25
41
  }
26
42
 
27
- // A public object containing pairs of color-name => color hex.
28
- // Accessible from anywhere via `this.$waveui.colors`.
29
- // These colors generate the CSS in `w-app` on mounted.
30
- colors = colors.reduce((obj, color) => {
31
- obj[color.label] = color.color
32
- color.shades.forEach(shade => (obj[shade.label] = shade.color))
33
- return obj
34
- }, { ...config.colors, black: '#000', white: '#fff', transparent: 'transparent', inherit: 'inherit' })
35
-
36
- config = {} // Store and expose the config in the $waveui object.
37
-
38
43
  static install (Vue, options = {}) {
39
44
  // Register directives.
40
45
  // for (const id in directives) {
@@ -57,6 +62,8 @@ export default class WaveUI {
57
62
  const { components = {} } = options || {}
58
63
  for (let id in components) {
59
64
  const component = components[id]
65
+ // If presets are defined for this component inject them into the props defaults.
66
+ if (options.presets?.[component.name]) injectPresets(component, options.presets[component.name])
60
67
  Vue.component(component.name, component)
61
68
  }
62
69
 
@@ -74,53 +81,22 @@ export default class WaveUI {
74
81
  constructor (options = {}) {
75
82
  if (WaveUI.instance) return WaveUI.instance
76
83
 
77
- else {
78
- this.#notificationManager = new NotificationManager()
79
-
80
- // Merge user options into the default config.
81
- mergeConfig(options)
82
-
83
- // Add color shades for each custom color given in options.
84
- if (config.css.colorShades) {
85
- config.colorShades = {}
86
-
87
- for (let color in config.colors) {
88
- color = { label: color, color: config.colors[color].replace('#', '') }
89
- const col = color.color
90
- if (col.length === 3) color.color = col[0] + '' + col[0] + col[1] + col[1] + col[2] + col[2]
84
+ this.$waveui._notificationManager = new NotificationManager()
91
85
 
92
- this.colors[color.label] = `#${color.color}`
86
+ // Merge user options into the default config.
87
+ this.$waveui.config = mergeConfig(options)
88
+ const config = this.$waveui.config
93
89
 
94
- for (let i = 1; i <= 3; i++) {
95
- const lighterColor = shadeColor(`#${color.color}`, i * 40)
96
- const darkerColor = shadeColor(`#${color.color}`, -i * 40)
97
- this.colors[`${color.label}-light${i}`] = lighterColor
98
- this.colors[`${color.label}-dark${i}`] = darkerColor
90
+ // Generates color shades for each color of each theme and store in the config.colors object.
91
+ if (config.css.colorShades) generateColorShades(config)
92
+ this.$waveui.colors = flattenColors(config.colors, colorPalette)
99
93
 
100
- // Adding the shades to the config object to generate the CSS from w-app.
101
- config.colorShades[`${color.label}-light${i}`] = lighterColor
102
- config.colorShades[`${color.label}-dark${i}`] = darkerColor
103
- }
104
- }
105
- }
106
-
107
- this.config = config
108
- this.notify = (...args) => notificationManager.notify(...args)
109
- WaveUI.instance = this
94
+ WaveUI.instance = this
110
95
 
111
- // Make waveui reactive and expose the single instance in Vue.
112
- WaveUI.vueInstance.prototype.$waveui = WaveUI.vueInstance.observable(this)
96
+ // Make Wave UI reactive and expose the single instance in the app.
97
+ const $waveui = WaveUI.vueInstance.observable(this.$waveui)
98
+ WaveUI.vueInstance.prototype.$waveui = $waveui
113
99
 
114
- delete WaveUI.vueInstance // Get rid of the Vue instance that we don't need anymore.
115
- }
116
- }
117
-
118
- notify (...args) {
119
- this.#notificationManager.notify(...args)
100
+ delete WaveUI.vueInstance // Get rid of the Vue instance that we don't need anymore.
120
101
  }
121
102
  }
122
-
123
- /**
124
- * Returns the WaveUI instance. Equivalent to using `$waveui` inside templates.
125
- */
126
- export const useWaveUI = () => inject('$waveui')
@@ -170,7 +170,7 @@
170
170
 
171
171
  // Sizes.
172
172
  // ----------------------------------------------
173
- // In all the sizes bellow, round(x / 2) * 2 to always have even numbers.
173
+ // In all the sizes below, round(x / 2) * 2 to always have even numbers.
174
174
  // Different heights with a mix of odd and even numbers will misalign
175
175
  // when vertically centering (vertical-align or align-items center).
176
176
  .size--xs {font-size: round(0.85 * $base-font-size);}
@@ -1,4 +1,54 @@
1
- export default [
1
+ const shadeColor = (color, amount) => {
2
+ return '#' + color.slice(1).match(/../g)
3
+ .map(x => {
4
+ x = +`0x${x}` + amount
5
+ return (x < 0 ? 0 : (x > 255 ? 255 : x)).toString(16).padStart(2, 0)
6
+ })
7
+ .join('')
8
+ }
9
+
10
+ /**
11
+ * Generates the color shades for each custom color and status colors for the current theme (only),
12
+ * and save it in the config object.
13
+ *
14
+ * @param {Object} config
15
+ */
16
+ export const generateColorShades = config => {
17
+ config.shades = {}
18
+
19
+ for (let color in config.colors) {
20
+ color = { label: color, color: config.colors[color].replace('#', '') }
21
+ const col = color.color
22
+ if (col.length === 3) color.color = col[0] + '' + col[0] + col[1] + col[1] + col[2] + col[2]
23
+
24
+ for (let i = 1; i <= 3; i++) {
25
+ const lighterColor = shadeColor(`#${color.color}`, i * 40)
26
+ const darkerColor = shadeColor(`#${color.color}`, -i * 40)
27
+
28
+ // Adding the shades to the config object to generate the CSS from w-app.
29
+ config.shades[`${color.label}-light${i}`] = lighterColor
30
+ config.shades[`${color.label}-dark${i}`] = darkerColor
31
+ }
32
+ }
33
+ }
34
+
35
+ export const flattenColors = (themeColors, colorPalette) => {
36
+ const colors = {
37
+ ...colorPalette.reduce((obj, color) => {
38
+ obj[color.label] = color.color
39
+ const shades = (color.shades || []).reduce((obj, color) => {
40
+ obj[color.label] = color.color
41
+ return obj
42
+ }, {})
43
+ return { ...obj, ...shades }
44
+ }, { ...themeColors, ...themeColors.shades })
45
+ }
46
+ delete colors.shades
47
+
48
+ return colors
49
+ }
50
+
51
+ export const colorPalette = [
2
52
  {
3
53
  label: 'pink',
4
54
  color: '#e91e63',
@@ -342,5 +392,9 @@ export default [
342
392
  { label: 'grey-dark5', color: '#353535' },
343
393
  { label: 'grey-dark6', color: '#252525' }
344
394
  ]
345
- }
395
+ },
396
+ { label: 'black', color: '#000' },
397
+ { label: 'white', color: '#fff' },
398
+ { label: 'transparent', color: 'transparent' },
399
+ { label: 'inherit', color: 'inherit' }
346
400
  ]
@@ -11,7 +11,7 @@ const config = Vue.observable({
11
11
  },
12
12
  css: {
13
13
  // Generate shades for custom colors and status colors.
14
- // Palette color shades are always generated with palette.
14
+ // Note: the color palette shades are always generated separately from SCSS.
15
15
  colorShades: true,
16
16
 
17
17
  // Generate palette colors and palette color shades.
@@ -27,10 +27,10 @@ const config = Vue.observable({
27
27
  colors: {
28
28
  primary: '#234781',
29
29
  secondary: '#d3ebff',
30
- success: '#54b946',
31
- error: '#f65555',
30
+ info: '#3d9ff5',
32
31
  warning: '#f80',
33
- info: '#3d9ff5'
32
+ success: '#54b946',
33
+ error: '#f65555'
34
34
  },
35
35
  icons: [],
36
36
  iconsLigature: false,
@@ -44,9 +44,18 @@ const config = Vue.observable({
44
44
  export { config as default }
45
45
 
46
46
  export const mergeConfig = (options, conf = config) => {
47
- for (const key in options) {
48
- const option = options[key]
49
- if (typeof option === 'object') mergeConfig(options[key], conf[key])
50
- else conf[key] = option
47
+ // If the conf object is empty, populate with options (case of presets).
48
+ if (!Object.keys(conf).length) conf = Object.assign(conf, options)
49
+
50
+ else {
51
+ for (const key in options) {
52
+ const option = options[key]
53
+ if (typeof option === 'object') {
54
+ mergeConfig(options[key], conf[key])
55
+ }
56
+ else conf[key] = option
57
+ }
51
58
  }
59
+
60
+ return conf
52
61
  }
@@ -19,8 +19,8 @@ const generateColors = config => {
19
19
  }
20
20
 
21
21
  // Color shades are generated in core.js, if the option is on.
22
- if (config.css.colorShades && config.colorShades) {
23
- Object.entries(config.colorShades).forEach(([label, color]) => {
22
+ if (config.css.colorShades && config.shades) {
23
+ Object.entries(config.shades).forEach(([label, color]) => {
24
24
  styles +=
25
25
  `${cssScope} .${label}--bg{background-color:${color}}` +
26
26
  `${cssScope} .${label}{color:${color}}`
@@ -1,7 +1,7 @@
1
1
  import Vue from 'vue'
2
2
 
3
3
  export default class NotificationManager {
4
- static instance
4
+ static #instance
5
5
  notifications
6
6
  // Private fields.
7
7
  #uid // A unique ID for each notification.
@@ -9,9 +9,9 @@ export default class NotificationManager {
9
9
 
10
10
  constructor () {
11
11
  // Singleton pattern.
12
- if (NotificationManager.instance) return NotificationManager.instance
12
+ if (NotificationManager.#instance) return NotificationManager.#instance
13
13
 
14
- NotificationManager.instance = this
14
+ NotificationManager.#instance = this
15
15
  this.notifications = []
16
16
  this.#uid = 0
17
17
  this.#notificationDefaults = {