wave-ui 3.13.6 → 3.14.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,7 +1,7 @@
1
1
  {
2
2
  "name": "wave-ui",
3
- "version": "3.13.6",
4
- "description": "An emerging UI framework for Vue.js (2 & 3) with only the bright side. :sunny:",
3
+ "version": "3.14.0",
4
+ "description": "An emerging UI framework for Vue.js 3 (and 2) with only the bright side. :sunny:",
5
5
  "author": "Antoni Andre <antoniandre.web@gmail.com>",
6
6
  "homepage": "https://antoniandre.github.io/wave-ui",
7
7
  "repository": "https://github.com/antoniandre/wave-ui",
@@ -1,8 +1,8 @@
1
1
  <template lang="pug">
2
2
  component.w-button(
3
- :is="route ? 'a' : 'button'"
3
+ :is="!disabled && route ? 'a' : 'button'"
4
4
  :type="!route && type"
5
- :href="(route && (externalLink ? route : resolvedRoute)) || null"
5
+ :href="(!disabled && route && (externalLink ? route : resolvedRoute)) || null"
6
6
  :class="classes"
7
7
  :disabled="!!disabled || null"
8
8
  v-bind="attrs"
@@ -75,20 +75,22 @@ export default {
75
75
  return this.hasRouter ? this.$router.resolve(this.route).href : this.route
76
76
  },
77
77
  attrs () {
78
+ const isValidRouterLink = this.route && this.hasRouter && !this.forceLink && !this.externalLink
78
79
  // If the button is a router-link, we can't apply events on it since vue-router needs the .native
79
80
  // modifier but it's not available with the v-on directive.
80
81
  // So do a manual router.push if $router is present.
81
- const attrsForRouterLink = {
82
- ...this.$attrs,
83
- onClick: e => {
84
- if (this.$attrs.onClick) this.$attrs.onClick(e)
82
+ const onRouterLinkClick = e => {
83
+ if (this.$attrs.onClick) this.$attrs.onClick(e)
84
+
85
+ this.$router.push(this.route)
86
+ e.stopPropagation() // If going to a route, no need to bubble up the event.
87
+ e.preventDefault()
88
+ }
85
89
 
86
- this.$router.push(this.route)
87
- e.stopPropagation() // If going to a route, no need to bubble up the event.
88
- e.preventDefault()
89
- }
90
+ return {
91
+ ...this.$attrs,
92
+ onClick: !this.disabled && (isValidRouterLink ? onRouterLinkClick : this.$attrs.onClick)
90
93
  }
91
- return this.route && this.hasRouter && !this.forceLink && !this.externalLink ? attrsForRouterLink : this.$attrs
92
94
  },
93
95
  size () {
94
96
  return (
@@ -1,11 +1,4 @@
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
- }
1
+ import { consoleError } from './console'
9
2
 
10
3
  /**
11
4
  * Generates the color shades for each custom color and status colors for the current theme (only),
@@ -19,15 +12,18 @@ export const generateColorShades = config => {
19
12
  const themeOfColors = config.colors[theme]
20
13
  themeOfColors.shades = {}
21
14
 
22
- for (let color in themeOfColors) {
23
- if (color === 'shades') continue // Skip if item is the `shades` container.
24
- color = { label: color, color: themeOfColors[color].replace('#', '') }
15
+ for (const label in themeOfColors) {
16
+ if (label === 'shades') continue // Skip if item is the `shades` container.
17
+
18
+ // Account for string colors and the fine tuned shaded colors
19
+ const colorInfo = themeOfColors[label]
20
+ const color = { label, color: (themeOfColors[label]?.color ?? themeOfColors[label]).replace('#', '') }
25
21
  const col = color.color
26
22
  if (col.length === 3) color.color = col[0] + '' + col[0] + col[1] + col[1] + col[2] + col[2]
27
23
 
28
- for (let i = 1; i <= 3; i++) {
29
- const lighterColor = shadeColor(`#${color.color}`, i * 40)
30
- const darkerColor = shadeColor(`#${color.color}`, -i * 40)
24
+ for (let i = 1; i <= 6; i++) {
25
+ const lighterColor = lighten(`#${color.color}`, i * (colorInfo?.lightIncrement ?? 16) + (colorInfo?.lightOffset ?? 0))
26
+ const darkerColor = darken(`#${color.color}`, i * (colorInfo?.darkIncrement ?? 12.4) + (colorInfo?.darkOffset ?? 0))
31
27
  // Adding the shades to the config object to generate the CSS from w-app.
32
28
  themeOfColors.shades[`${color.label}-light${i}`] = lighterColor
33
29
  themeOfColors.shades[`${color.label}-dark${i}`] = darkerColor
@@ -52,6 +48,230 @@ export const flattenColors = (themeColors, colorPalette) => {
52
48
  return colors
53
49
  }
54
50
 
51
+ /**
52
+ * Clamp a value between a minimum and maximum value.
53
+ *
54
+ * @param {number} value - Value to clamp
55
+ * @param {number} min - Minimum possible value
56
+ * @param {number} max - Maximum possible value
57
+ * @returns {number} The clamped value
58
+ */
59
+ export function clamp (value, min, max) {
60
+ return Math.min(Math.max(value, min), max)
61
+ }
62
+
63
+ /**
64
+ * Convert a number to a hexadecimal string.
65
+ *
66
+ * @param {number} value - The number to convert
67
+ * @throws {Error} - If the value is not in the range 0~255
68
+ * @returns {string} The hexadecimal string
69
+ */
70
+ export function toHexString (value) {
71
+ const trimmedValue = value.toString(16)
72
+
73
+ return (
74
+ (trimmedValue.length === 1 && `0${trimmedValue}`) ||
75
+ (trimmedValue.length === 2 && trimmedValue) ||
76
+ consoleError(`expected value from 0~255, got: ${value}`) ||
77
+ ''
78
+ )
79
+ }
80
+
81
+ /**
82
+ * Determines if a string is a valid hex color
83
+ *
84
+ * Valid long hex colors formats:
85
+ * - #ffffff,
86
+ * - #00000033
87
+ *
88
+ * @param {string} str - The string to test
89
+ * @returns {boolean} - Whether the string is a valid hex color
90
+ */
91
+ export function isValidHex (str) {
92
+ return /^#[0-9a-f]{6}([0-9a-f]{2})?$/i.test(str)
93
+ }
94
+
95
+ /**
96
+ * Determines if a string is a short hex color
97
+ *
98
+ * Valid short hex colors formats:
99
+ * - #fff,
100
+ * - #0003
101
+ *
102
+ * @param {string} str - The string to test
103
+ * @returns {boolean} - Whether the string is a short hex color
104
+ */
105
+ export function isShortHex (str) {
106
+ return /^#[0-9a-f]{3}([0-9a-f])?$/i.test(str)
107
+ }
108
+
109
+ /**
110
+ * Expands a short hex color to a full hex color
111
+ *
112
+ * @param {string} str - The short hex color to expand such as '#fff' or '#0003' with an alpha value
113
+ * @returns {string} - The expanded hex color such as '#ffffff' or '#00000033' with an alpha value
114
+ */
115
+ export function expandShortHex (str) {
116
+ return `#${str.substring(1).split('').map(char => `${char}${char}`).join('')}`
117
+ }
118
+
119
+ /**
120
+ * Parse a color string into a full length hex string
121
+ *
122
+ * @param {string} str - The color string to parse, either a full hex color or short hex color e.g. '#fff' or '#001122'.
123
+ * This can also include an alpha value e.g. '#00112233' or '#0003'.
124
+ * @throws {Error} - If the string is not a valid color
125
+ * @returns {string} - The full parsed hex color for example, with alpha: '#00112233'
126
+ */
127
+ function getColorFullHex (str) {
128
+ return (isValidHex(str) && str) || (isShortHex(str) && expandShortHex(str)) || consoleError(`expected color hex string, got '${str}'`) || ''
129
+ }
130
+
131
+ /**
132
+ * Break a hex color string into it's components
133
+ *
134
+ * @param {string} color - The color string to parse
135
+ * @returns {{ red, green, blue, alpha, hasAlpha: boolean }} - The color components
136
+ */
137
+ function getColorComponents (color) {
138
+ const hex = getColorFullHex(color)
139
+
140
+ const red = parseInt(hex.substring(1, 3), 16)
141
+ const green = parseInt(hex.substring(3, 5), 16)
142
+ const blue = parseInt(hex.substring(5, 7), 16)
143
+
144
+ // Compare against length 9 because the full hex string will have the `#` at the beginning of it
145
+ const alpha = hex.length === 9
146
+ ? parseInt(hex.substring(7, 9), 16) / 255
147
+ : 1.0
148
+
149
+ return {
150
+ red,
151
+ green,
152
+ blue,
153
+ alpha,
154
+ hasAlpha: hex.length === 9
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Convert RGB components to a hex color string
160
+ *
161
+ * @param {number} r - red
162
+ * @param {number} g - green
163
+ * @param {number} b - blue
164
+ * @param {number} [a] - alpha
165
+ * @returns {string} - The hex color string for example: '#001122' or with alpha: '#00112233'
166
+ */
167
+ export function hexFromRGB (r, g, b, a) {
168
+ return `#${toHexString(r)}${toHexString(g)}${toHexString(b)}${a ? toHexString(Math.floor(a * 255)) : ''}`
169
+ }
170
+
171
+ /**
172
+ * Mix two colors together. The same way that SCSS does it
173
+ *
174
+ * Valid hex colors formats:
175
+ * - #fff
176
+ * - #0003
177
+ * - #ffffff
178
+ * - #00000033
179
+ *
180
+ * @param {string} color1 - Color 1 as a hex
181
+ * @param {string} color2 - Color 2 as a hex
182
+ * @param {number} [weight] - The percentage to mix them at. Default: 50
183
+ * @returns {string} - The new mixed color as a hex for example: '#001122' or with alpha: '#00112233'
184
+ *
185
+ * @see https://gist.github.com/ktnyt/2573047b5b4c7c775f2be22326ebf6a8 for the original Typescript implementation
186
+ */
187
+ export function mixColors (color1, color2, weight = 50) {
188
+ const c1 = getColorComponents(color1)
189
+ const c2 = getColorComponents(color2)
190
+
191
+ const percentage = clamp(weight, 0, 100) / 100
192
+ const alphaWeight = 2 * percentage - 1
193
+
194
+ const alphaDiff = c1.alpha - c2.alpha
195
+ const c1Weight = (
196
+ (
197
+ alphaWeight * alphaDiff === -1
198
+ ? alphaWeight
199
+ : (alphaWeight + alphaDiff) / (1 + alphaWeight * alphaDiff)
200
+ ) + 1
201
+ ) / 2
202
+ const c2Weight = 1 - c1Weight
203
+
204
+ const red = clamp(Math.round(c1.red * c1Weight + c2.red * c2Weight), 0, 255)
205
+ const green = clamp(Math.round(c1.green * c1Weight + c2.green * c2Weight), 0, 255)
206
+ const blue = clamp(Math.round(c1.blue * c1Weight + c2.blue * c2Weight), 0, 255)
207
+
208
+ const alpha = c1.alpha * percentage + c2.alpha * (1 - percentage)
209
+
210
+ return c1.hasAlpha || c2.hasAlpha || alpha !== 1
211
+ ? hexFromRGB(red, green, blue, alpha)
212
+ : hexFromRGB(red, green, blue)
213
+ }
214
+
215
+ /**
216
+ * Lighten a color by a percentage
217
+ *
218
+ * Valid hex colors formats:
219
+ * - #fff
220
+ * - #0003
221
+ * - #ffffff
222
+ * - #00000033
223
+ *
224
+ * @param {string} color - The hex color to lighten
225
+ * @param {number} [weight] - The amount to lighten by.
226
+ * Default: `15` to match the SCSS `light-increment` value of `7.5`
227
+ * the way the math is handled here we double the weight to match the SCSS result
228
+ * @returns {string} - The new lightened color as a full hex string, potentially with alpha
229
+ */
230
+ export function lighten (color, weight = 15) {
231
+ return mixColors('#ffffff', color, weight)
232
+ }
233
+
234
+ /**
235
+ * Darken a color by a percentage
236
+ *
237
+ * Valid hex colors formats:
238
+ * - #fff
239
+ * - #0003
240
+ * - #ffffff
241
+ * - #00000033
242
+ *
243
+ * @param {string} color - The hex color to darken
244
+ * @param {number} [weight] - The amount to darken by.
245
+ * Default: `12.4` to match the SCSS `dark-increment` value of `6.2`
246
+ * the way the math is handled here we double the weight to match the SCSS result
247
+ * @returns {string} - The new darkened color as a full hex string, potentially with alpha
248
+ */
249
+ export function darken (color, weight = 12.4) {
250
+ return mixColors('#000000', color, weight)
251
+ }
252
+
253
+ /**
254
+ * Generate a color palette from a color info object
255
+ *
256
+ * @param {Record<string,{color: string, lightOffset: number, lightIncrement: number, darkOffset: number, darkIncrement: number}>} colorInfo - Colors to generate the palette from
257
+ * @returns {Array<{label: string, color: string, shades: Array<{label: string, color: string}>}>} - The color palette
258
+ */
259
+ export function generateColorPalette (colorInfo) {
260
+ return Object.keys(colorInfo).map(label => {
261
+ const color = colorInfo[label]?.color ?? colorInfo[label]
262
+ return {
263
+ label,
264
+ color,
265
+ shades: [6, 5, 4, 3, 2, 1, -1, -2, -3, -4, -5, -6].map(i => ({
266
+ label: `${label}-${i > 0 ? 'light' : 'dark'}${Math.abs(i)}`,
267
+ color: i > 0
268
+ ? lighten(color, (i * (colorInfo[label]?.lightIncrement ?? 15)) - (colorInfo[label]?.lightOffset ?? 0))
269
+ : darken(color, (-i * (colorInfo[label]?.darkIncrement ?? 12.4)) - (colorInfo[label]?.darkOffset ?? 0))
270
+ }))
271
+ }
272
+ })
273
+ }
274
+
55
275
  export const colorPalette = [
56
276
  {
57
277
  label: 'pink',
@@ -323,7 +543,6 @@ export const colorPalette = [
323
543
  { label: 'deep-orange-dark6', color: '#661f00' }
324
544
  ]
325
545
  },
326
-
327
546
  {
328
547
  label: 'red',
329
548
  color: '#fa3317',
@@ -68,7 +68,7 @@ export const mergeConfig = (options, conf = config) => {
68
68
  else {
69
69
  for (const key in options) {
70
70
  const option = options[key]
71
- if (typeof option === 'object') {
71
+ if (typeof option === 'object' && typeof conf[key] === 'object') {
72
72
  mergeConfig(options[key], conf[key])
73
73
  }
74
74
  else conf[key] = option
@@ -41,7 +41,7 @@ const generateColors = (themeColors, generateShadeCssVariables) => {
41
41
  // Status colors must remain after the other colors so they have priority in form validations.
42
42
  // That only makes sense when there are 2 colors on the same element: e.g. `span.primary.error`.
43
43
  const allColors = { ...colors, info, warning, success, error }
44
- for (const colorName in allColors) cssVariables[colorName] = allColors[colorName]
44
+ for (const colorName in allColors) cssVariables[colorName] = allColors[colorName]?.color ?? allColors[colorName]
45
45
  if (generateShadeCssVariables) {
46
46
  for (const colorName in shades) cssVariables[colorName] = shades[colorName]
47
47
  }
@@ -272,12 +272,12 @@ export const injectCSSInDOM = $waveui => {
272
272
  window.addEventListener('resize', () => getBreakpoint($waveui))
273
273
  }
274
274
 
275
- export const injectColorsCSSInDOM = (themeColors, generateShadeCssVariables) => {
275
+ export const injectColorsCSSInDOM = (themeColors, colorPalette, generateShadeCssVariables) => {
276
276
  // Inject global dynamic CSS classes in document head.
277
277
  if (!document.getElementById('wave-ui-colors')) {
278
278
  const css = document.createElement('style')
279
279
  css.id = 'wave-ui-colors'
280
- css.innerHTML = generateColors(themeColors, generateShadeCssVariables)
280
+ css.innerHTML = generateColors(themeColors, colorPalette, generateShadeCssVariables)
281
281
 
282
282
  const firstStyle = document.head.querySelectorAll('style,link[rel="stylesheet"]')[0]
283
283
  if (firstStyle) firstStyle.before(css)