wave-ui 3.16.3 → 3.17.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": "3.16.3",
3
+ "version": "3.17.0",
4
4
  "description": "A 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",
@@ -1,4 +1,5 @@
1
- export { default as WAccordion } from './w-accordion.vue'
1
+ export { default as WAccordion } from './w-accordion/index.vue'
2
+ export { default as WAccordionItem } from './w-accordion/item.vue'
2
3
  export { default as WAlert } from './w-alert.vue'
3
4
  export { default as WAutocomplete } from './w-autocomplete.vue'
4
5
  export { default as WApp } from './w-app.vue'
@@ -0,0 +1,205 @@
1
+ <template lang="pug">
2
+ .w-accordion(:class="accordionClasses")
3
+ //- When externally using the WAccordionItem component.
4
+ slot(v-if="accordionItemsProvided")
5
+
6
+ //- Else, when providing the items array or number through the items prop.
7
+ template(v-else-if="!isNaN(items) || (items || []).length")
8
+ w-accordion-item(
9
+ v-for="(accordionItem, i) in accordionItems"
10
+ :key="i"
11
+ :class="itemClass"
12
+ :item="accordionItem"
13
+ @focus="$emit('focus', $event)")
14
+ //- Title.
15
+ template(#title="{ item, expanded, index }")
16
+ slot(
17
+ v-if="$slots[`item-title.${accordionItem.id || (accordionItem._index + 1)}`]"
18
+ :name="`item-title.${accordionItem.id || (accordionItem._index + 1)}`"
19
+ :item="item"
20
+ :expanded="expanded"
21
+ :index="index")
22
+ slot(
23
+ v-else
24
+ name="item-title"
25
+ :item="item"
26
+ :expanded="expanded"
27
+ :index="index")
28
+ //- Content.
29
+ template(#content="{ item, expanded, index }")
30
+ slot(
31
+ v-if="$slots[`item-content.${accordionItem.id || (accordionItem._index + 1)}`]"
32
+ :name="`item-content.${accordionItem.id || (accordionItem._index + 1)}`"
33
+ :item="item"
34
+ :expanded="expanded"
35
+ :index="index")
36
+ slot(
37
+ v-else
38
+ name="item-content"
39
+ :item="item"
40
+ :expanded="expanded"
41
+ :index="index")
42
+ </template>
43
+
44
+ <script>
45
+ import { objectifyClasses } from '../../utils/index'
46
+ import WAccordionItem from './item.vue'
47
+
48
+ export default {
49
+ name: 'w-accordion',
50
+
51
+ props: {
52
+ modelValue: { type: Array },
53
+ color: { type: String },
54
+ bgColor: { type: String },
55
+ items: { type: [Array, Number] }, // Required unless externally using WAccordionItem.
56
+ itemColorKey: { type: String, default: 'color' }, // Support a different color per item.
57
+ itemTitleKey: { type: String, default: 'title' },
58
+ itemContentKey: { type: String, default: 'content' },
59
+ itemClass: { type: [String, Array, Object] },
60
+ titleClass: { type: [String, Array, Object] },
61
+ contentClass: { type: [String, Array, Object] },
62
+ expandIcon: { type: [String, Boolean], default: 'wi-triangle-down' },
63
+ expandIconRight: { type: Boolean },
64
+ expandIconRotate90: { type: Boolean },
65
+ expandIconProps: { type: Object, default: () => ({}) },
66
+ expandSingle: { type: Boolean },
67
+ collapseIcon: { type: String },
68
+ shadow: { type: Boolean },
69
+ duration: { type: Number, default: 250 },
70
+ dark: { type: Boolean },
71
+ light: { type: Boolean }
72
+ },
73
+
74
+ components: { WAccordionItem },
75
+
76
+ emits: ['input', 'update:modelValue', 'focus', 'item-expand', 'item-collapsed'],
77
+
78
+ // All provided to the WAccordionItem, not passed as prop since it could be used externally.
79
+ provide () {
80
+ return {
81
+ itemClasses: objectifyClasses(this.itemClasses),
82
+ titleClasses: this.titleClasses,
83
+ contentClasses: this.contentClasses,
84
+ toggleItem: this.toggleItem,
85
+ onEndOfCollapse: this.onEndOfCollapse,
86
+ getOriginalItem: this.getOriginalItem,
87
+ options: this.$props,
88
+ registerItem: this.registerItem
89
+ }
90
+ },
91
+
92
+ data: () => ({
93
+ accordionItems: [],
94
+ registeredAccordionItems: [] // When using w-accordion-item component and not items prop.
95
+ }),
96
+
97
+ computed: {
98
+ // Detect if the accordion items are directly provided through slot using WAccordionItem.
99
+ accordionItemsProvided () {
100
+ return this.$slots.default?.()?.some(item => item?.type?.name)
101
+ },
102
+
103
+ accordionClasses () {
104
+ return {
105
+ [this.color]: this.color,
106
+ [`${this.bgColor}--bg`]: this.bgColor,
107
+ 'w-accordion--dark': this.dark,
108
+ 'w-accordion--light': this.light,
109
+ 'w-accordion--shadow': this.shadow,
110
+ 'w-accordion--no-icon': !this.expandIcon && !this.collapseIcon,
111
+ 'w-accordion--icon-right': this.expandIcon && this.expandIconRight,
112
+ 'w-accordion--rotate-icon': this.expandIcon && !this.collapseIcon
113
+ }
114
+ },
115
+
116
+ itemClasses () {
117
+ return objectifyClasses(this.itemClass)
118
+ },
119
+
120
+ titleClasses () {
121
+ return objectifyClasses(this.titleClass)
122
+ },
123
+
124
+ contentClasses () {
125
+ return objectifyClasses(this.contentClass)
126
+ }
127
+ },
128
+
129
+ methods: {
130
+ toggleItem (item, e) {
131
+ item._expanded = !item._expanded
132
+
133
+ const items = this.registeredAccordionItems.length ? this.registeredAccordionItems : this.accordionItems
134
+ if (this.expandSingle) items.forEach(obj => obj._index !== item._index && (obj._expanded = false))
135
+ const expandedItems = items.map(item => item._expanded || false)
136
+ this.$emit('update:modelValue', expandedItems)
137
+ this.$emit('input', expandedItems)
138
+ this.$emit('item-expand', { item, expanded: item._expanded })
139
+
140
+ // When a focused item moves in the page, the scrollTop is naturally updated by the browser.
141
+ // So if expandSingle is set to true, clicking on the next title of an open pane would shift the
142
+ // scrollTop unless unfocused while transitioning. #3.
143
+ e.target.blur()
144
+ setTimeout(() => e.target.focus(), 300)
145
+ },
146
+
147
+ onEndOfCollapse (item) {
148
+ this.$emit('item-collapsed', { item, expanded: item._expanded })
149
+ },
150
+
151
+ // Return the original accordion item (so there is no `_index`, etc.).
152
+ getOriginalItem (item) {
153
+ return (this.registeredAccordionItems.length ? this.registeredAccordionItems : this.items)[item._index]
154
+ },
155
+
156
+ updateItems () {
157
+ let items = []
158
+ if (this.registeredAccordionItems.length) items = this.registeredAccordionItems
159
+ else items = (typeof this.items === 'number' ? Array(this.items).fill({}) : this.items) || []
160
+
161
+ this.accordionItems = items.map((item, _index) => ({
162
+ ...item,
163
+ _index,
164
+ _expanded: this.modelValue && this.modelValue[_index],
165
+ _disabled: !!item.disabled
166
+ }))
167
+ },
168
+
169
+ // Provide-injected in and used from w-accordion-item.
170
+ // Only when w-accordion-item is directly used outside of Wave UI.
171
+ registerItem (item) {
172
+ if (!this.items) {
173
+ item._index = this.registeredAccordionItems.length
174
+ item._expanded = this.modelValue?.[item._index] || false
175
+
176
+ this.registeredAccordionItems.push(item)
177
+ this.updateItems()
178
+ }
179
+ }
180
+ },
181
+
182
+ created () {
183
+ this.updateItems()
184
+ },
185
+
186
+ watch: {
187
+ modelValue () {
188
+ this.updateItems()
189
+ },
190
+ items () {
191
+ this.updateItems()
192
+ }
193
+ }
194
+ }
195
+ </script>
196
+
197
+ <style lang="scss">
198
+ .w-accordion {
199
+ z-index: 1;
200
+
201
+ @include themeable;
202
+
203
+ &--shadow {box-shadow: $box-shadow;}
204
+ }
205
+ </style>
@@ -0,0 +1,149 @@
1
+ <template lang="pug">
2
+ .w-accordion__item(:class="itemClasses" :aria-expanded="accordionItem._expanded ? 'true' : 'false'")
3
+ .w-accordion__item-title(
4
+ @click="!accordionItem._disabled && toggleItem(accordionItem, $event)"
5
+ @focus="$emit('focus', getOriginalItem(accordionItem))"
6
+ @keypress.enter="!accordionItem._disabled && toggleItem(accordionItem, $event)"
7
+ :tabindex="!accordionItem._disabled && 0"
8
+ :class="titleClasses")
9
+ //- Expand icon on left.
10
+ w-button.w-accordion__expand-icon(
11
+ v-if="options.expandIcon && !options.expandIconRight"
12
+ :icon="(accordionItem._expanded && options.collapseIcon) || options.expandIcon"
13
+ :icon-props="options.expandIconProps"
14
+ :disabled="accordionItem._disabled || null"
15
+ :tabindex="-1"
16
+ text
17
+ @keypress.stop
18
+ @click.stop="!accordionItem._disabled && toggleItem(accordionItem, $event)"
19
+ :class="{ 'w-accordion__expand-icon--expanded': accordionItem._expanded, 'w-accordion__expand-icon--rotate90': options.expandIconRotate90 }")
20
+ //- Title.
21
+ slot(
22
+ name="title"
23
+ :item="getOriginalItem(accordionItem)"
24
+ :expanded="accordionItem._expanded"
25
+ :index="accordionItem._index + 1")
26
+ .grow(v-html="accordionItem[options.itemTitleKey]")
27
+ //- Expand icon on right.
28
+ w-button.w-accordion__expand-icon(
29
+ v-if="options.expandIcon && options.expandIconRight"
30
+ :icon="(accordionItem._expanded && options.collapseIcon) || options.expandIcon"
31
+ text
32
+ @keypress.stop
33
+ @click.stop="!accordionItem._disabled && toggleItem(accordionItem, $event)"
34
+ :class="{ 'w-accordion__expand-icon--expanded': accordionItem._expanded, 'w-accordion__expand-icon--rotate90': options.expandIconRotate90 }")
35
+ //- Content.
36
+ w-transition-expand(y @after-leave="onEndOfCollapse(accordionItem)" :duration="options.duration")
37
+ .w-accordion__item-content(v-if="accordionItem._expanded" :class="contentClasses")
38
+ slot(
39
+ name="content"
40
+ :item="getOriginalItem(accordionItem)"
41
+ :expanded="accordionItem._expanded"
42
+ :index="accordionItem._index + 1")
43
+ div(v-html="accordionItem[options.itemContentKey]")
44
+ </template>
45
+
46
+ <script>
47
+ export default {
48
+ name: 'w-accordion-item',
49
+
50
+ props: {
51
+ item: { type: Object }
52
+ },
53
+
54
+ inject: [
55
+ 'options',
56
+ 'itemClasses',
57
+ 'titleClasses',
58
+ 'contentClasses',
59
+ 'toggleItem',
60
+ 'onEndOfCollapse',
61
+ 'getOriginalItem',
62
+ 'registerItem'
63
+ ],
64
+
65
+ emits: ['focus'],
66
+
67
+ data () {
68
+ return {
69
+ accordionItem: {
70
+ ...(this.item || {}),
71
+ _index: this.item?._index ?? 0,
72
+ _expanded: this.item?._expanded || false,
73
+ _disabled: this.item?._disabled || false
74
+ }
75
+ }
76
+ },
77
+
78
+ computed: {
79
+ itemClass () {
80
+ return {
81
+ 'w-accordion__item--expanded': this.accordionItem._expanded,
82
+ 'w-accordion__item--disabled': this.accordionItem._disabled,
83
+ [this.accordionItem[this.options.itemColorKey]]: this.accordionItem[this.options.itemColorKey],
84
+ ...this.accordionItemClasses
85
+ }
86
+ }
87
+ },
88
+
89
+ created () {
90
+ // Register this item to the w-accordion component.
91
+ this.registerItem(this.accordionItem)
92
+ }
93
+ }
94
+ </script>
95
+
96
+ <style lang="scss">
97
+ .w-accordion__item {position: relative;}
98
+
99
+ button.w-accordion__expand-icon {color: rgba(var(--w-base-color-rgb), 0.4);}
100
+ .w-accordion__expand-icon {
101
+ margin-right: $base-increment;
102
+
103
+ .w-accordion--rotate-icon & {@include default-transition;}
104
+ &--rotate90 {transform: rotate(-90deg);}
105
+ &--expanded {transform: rotate(-180deg);}
106
+ &--expanded.w-accordion__expand-icon--rotate90 {transform: rotate(0deg);}
107
+
108
+ .w-icon:before {font-size: 1.1em;}
109
+ }
110
+
111
+ .w-accordion__item-title {
112
+ position: relative;
113
+ display: flex;
114
+ align-items: center;
115
+ font-size: round(1.2 * $base-font-size);
116
+ padding: 1 * $base-increment;
117
+ user-select: none;
118
+ cursor: pointer;
119
+ border-top: $border;
120
+ -webkit-tap-highlight-color: transparent;
121
+
122
+ .w-accordion__item--disabled & {
123
+ cursor: not-allowed;
124
+ opacity: 0.6;
125
+ -webkit-tap-highlight-color: transparent;
126
+ }
127
+ .w-accordion--no-icon &, .w-accordion--icon-right & {padding-left: 3 * $base-increment;}
128
+
129
+ .w-accordion__item:first-child & {border-top-color: transparent;}
130
+
131
+ &:before {
132
+ content: '';
133
+ position: absolute;
134
+ inset: 0;
135
+ background-color: currentColor;
136
+ opacity: 0;
137
+ transition: $fast-transition-duration;
138
+ }
139
+
140
+ &:focus:before, &:hover:before {opacity: 0.03;}
141
+ &:active:before {opacity: 0.05;}
142
+ .w-accordion__item--disabled &:before {display: none;}
143
+ }
144
+
145
+ .w-accordion__item-content {
146
+ padding: (2 * $base-increment) (3 * $base-increment);
147
+ }
148
+
149
+ </style>
@@ -53,6 +53,8 @@ export default {
53
53
  light: { type: Boolean }
54
54
  },
55
55
 
56
+ emits: ['input', 'update:modelValue', 'before-close', 'close'],
57
+
56
58
  provide () {
57
59
  return {
58
60
  // If a detachable is used inside a w-drawer without an appendTo, default to the drawer element
@@ -61,8 +63,6 @@ export default {
61
63
  }
62
64
  },
63
65
 
64
- emits: ['input', 'update:modelValue', 'before-close', 'close'],
65
-
66
66
  data () {
67
67
  return {
68
68
  showWrapper: this.modelValue,
@@ -82,6 +82,8 @@ export default {
82
82
  light: { type: Boolean }
83
83
  },
84
84
 
85
+ emits: ['input', 'update:modelValue', 'before-close', 'close'],
86
+
85
87
  provide () {
86
88
  return {
87
89
  // If a detachable is used inside a w-drawer without an appendTo, default to the drawer element
@@ -90,8 +92,6 @@ export default {
90
92
  }
91
93
  },
92
94
 
93
- emits: ['input', 'update:modelValue', 'before-close', 'close'],
94
-
95
95
  data () {
96
96
  return {
97
97
  showWrapper: this.modelValue,
@@ -31,17 +31,6 @@ export default {
31
31
  readonly: { type: Boolean }
32
32
  },
33
33
 
34
- provide () {
35
- return {
36
- formRegister: this.register,
37
- formUnregister: this.unregister,
38
- validateElement: this.validateElement,
39
- // Give access to the form params (like disabled) to all the form components.
40
- // To keep it reactive, we need an object not a list of props (by design in Vue).
41
- formProps: this.$props
42
- }
43
- },
44
-
45
34
  emits: [
46
35
  'submit',
47
36
  'before-validate',
@@ -54,6 +43,17 @@ export default {
54
43
  'update:errorsCount'
55
44
  ],
56
45
 
46
+ provide () {
47
+ return {
48
+ formRegister: this.register,
49
+ formUnregister: this.unregister,
50
+ validateElement: this.validateElement,
51
+ // Give access to the form params (like disabled) to all the form components.
52
+ // To keep it reactive, we need an object not a list of props (by design in Vue).
53
+ formProps: this.$props
54
+ }
55
+ },
56
+
57
57
  data: () => ({
58
58
  formElements: [],
59
59
  status: null, // null = pristine, false = error, true = success.
@@ -82,6 +82,8 @@ export default {
82
82
  // alignRight, noPosition, zIndex, activator.
83
83
  },
84
84
 
85
+ emits: ['input', 'update:modelValue', 'open', 'close'],
86
+
85
87
  provide () {
86
88
  return {
87
89
  // If a detachable is used inside a w-menu without an appendTo, default to the menu element
@@ -90,8 +92,6 @@ export default {
90
92
  }
91
93
  },
92
94
 
93
- emits: ['input', 'update:modelValue', 'open', 'close'],
94
-
95
95
  data: () => ({
96
96
  detachableVisible: false,
97
97
  hoveringActivator: false,
@@ -27,6 +27,8 @@ export default {
27
27
  persistentNoAnimation: { type: Boolean }
28
28
  },
29
29
 
30
+ emits: ['input', 'update:modelValue', 'click', 'before-close', 'close'],
31
+
30
32
  provide () {
31
33
  return {
32
34
  // If a detachable is used inside a w-overlay without an appendTo, default to the overlay element
@@ -35,8 +37,6 @@ export default {
35
37
  }
36
38
  },
37
39
 
38
- emits: ['input', 'update:modelValue', 'click', 'before-close', 'close'],
39
-
40
40
  data: () => ({
41
41
  persistentAnimate: false,
42
42
  showOverlay: false