wave-ui 3.27.2 → 4.0.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.
Files changed (98) hide show
  1. package/dist/types/types/$waveui.d.ts +21 -1
  2. package/dist/types/types/colors.d.ts +2 -0
  3. package/dist/types/types/components/WAccordion.d.ts +99 -6
  4. package/dist/types/types/components/WAutocomplete.d.ts +437 -0
  5. package/dist/types/types/components/WBreadcrumbs.d.ts +7 -0
  6. package/dist/types/types/components/WButton.d.ts +7 -0
  7. package/dist/types/types/components/WCheckbox.d.ts +34 -0
  8. package/dist/types/types/components/WCheckboxes.d.ts +30 -0
  9. package/dist/types/types/components/WInput.d.ts +34 -0
  10. package/dist/types/types/components/WList.d.ts +7 -0
  11. package/dist/types/types/components/WMenu.d.ts +12 -6
  12. package/dist/types/types/components/WRadio.d.ts +34 -0
  13. package/dist/types/types/components/WRadios.d.ts +30 -0
  14. package/dist/types/types/components/WScrollable.d.ts +143 -0
  15. package/dist/types/types/components/WScrollable.js +2 -0
  16. package/dist/types/types/components/WSelect.d.ts +34 -0
  17. package/dist/types/types/components/WSwitch.d.ts +34 -0
  18. package/dist/types/types/components/WTable.d.ts +7 -0
  19. package/dist/types/types/components/WTabs.d.ts +7 -0
  20. package/dist/types/types/components/WTag.d.ts +7 -0
  21. package/dist/types/types/components/WTooltip.d.ts +20 -7
  22. package/dist/types/types/components/WTransitions.d.ts +104 -0
  23. package/dist/types/types/components/WTransitions.js +2 -0
  24. package/dist/types/types/components/WTree.d.ts +7 -0
  25. package/dist/types/types/components/index.d.ts +3 -1
  26. package/dist/wave-ui.cjs.js +3 -3
  27. package/dist/wave-ui.css +1 -1
  28. package/dist/wave-ui.esm.js +2190 -1350
  29. package/dist/wave-ui.umd.js +3 -3
  30. package/package.json +6 -6
  31. package/src/wave-ui/components/index.js +0 -1
  32. package/src/wave-ui/components/transitions/w-transition-bounce.vue +2 -1
  33. package/src/wave-ui/components/transitions/w-transition-expand.vue +3 -2
  34. package/src/wave-ui/components/transitions/w-transition-fade.vue +2 -1
  35. package/src/wave-ui/components/transitions/w-transition-scale-fade.vue +2 -1
  36. package/src/wave-ui/components/transitions/w-transition-scale.vue +2 -1
  37. package/src/wave-ui/components/transitions/w-transition-slide-fade.vue +2 -1
  38. package/src/wave-ui/components/transitions/w-transition-slide.vue +2 -1
  39. package/src/wave-ui/components/transitions/w-transition-twist.vue +2 -1
  40. package/src/wave-ui/components/w-accordion/index.vue +15 -6
  41. package/src/wave-ui/components/w-accordion/item.vue +71 -26
  42. package/src/wave-ui/components/w-alert.vue +27 -29
  43. package/src/wave-ui/components/w-autocomplete.vue +626 -192
  44. package/src/wave-ui/components/w-badge.vue +54 -53
  45. package/src/wave-ui/components/w-breadcrumbs.vue +20 -11
  46. package/src/wave-ui/components/w-button/button.vue +36 -24
  47. package/src/wave-ui/components/w-button/index.vue +6 -5
  48. package/src/wave-ui/components/w-card.vue +8 -7
  49. package/src/wave-ui/components/w-checkbox.vue +31 -11
  50. package/src/wave-ui/components/w-checkboxes.vue +21 -3
  51. package/src/wave-ui/components/w-confirm.vue +22 -22
  52. package/src/wave-ui/components/w-dialog.vue +1 -1
  53. package/src/wave-ui/components/w-divider.vue +5 -5
  54. package/src/wave-ui/components/w-drawer.vue +3 -3
  55. package/src/wave-ui/components/w-form-element.vue +2 -2
  56. package/src/wave-ui/components/w-icon.vue +12 -14
  57. package/src/wave-ui/components/w-image.vue +1 -1
  58. package/src/wave-ui/components/w-input.vue +43 -25
  59. package/src/wave-ui/components/w-list.vue +23 -12
  60. package/src/wave-ui/components/w-menu.vue +57 -55
  61. package/src/wave-ui/components/w-notification.vue +4 -4
  62. package/src/wave-ui/components/w-progress.vue +6 -7
  63. package/src/wave-ui/components/w-radio.vue +32 -7
  64. package/src/wave-ui/components/w-radios.vue +28 -3
  65. package/src/wave-ui/components/w-rating.vue +7 -9
  66. package/src/wave-ui/components/w-scrollable.vue +670 -97
  67. package/src/wave-ui/components/w-select.vue +119 -101
  68. package/src/wave-ui/components/w-slider.vue +26 -26
  69. package/src/wave-ui/components/w-spinner.vue +5 -7
  70. package/src/wave-ui/components/w-switch.vue +71 -47
  71. package/src/wave-ui/components/w-table.vue +69 -36
  72. package/src/wave-ui/components/w-tabs/index.vue +31 -24
  73. package/src/wave-ui/components/w-tag.vue +49 -38
  74. package/src/wave-ui/components/w-textarea.vue +22 -22
  75. package/src/wave-ui/components/w-timeline.vue +6 -6
  76. package/src/wave-ui/components/w-toolbar.vue +8 -8
  77. package/src/wave-ui/components/w-tooltip.vue +30 -25
  78. package/src/wave-ui/components/w-tree.vue +35 -16
  79. package/src/wave-ui/core.js +11 -1
  80. package/src/wave-ui/mixins/detachable.js +98 -43
  81. package/src/wave-ui/mixins/ripple.js +39 -0
  82. package/src/wave-ui/scss/_base.scss +82 -17
  83. package/src/wave-ui/scss/_colors.scss +6 -75
  84. package/src/wave-ui/scss/_layout.scss +39 -47
  85. package/src/wave-ui/scss/_ripple.scss +37 -0
  86. package/src/wave-ui/scss/_transitions.scss +19 -19
  87. package/src/wave-ui/scss/_typography.scss +8 -9
  88. package/src/wave-ui/scss/index.scss +1 -0
  89. package/src/wave-ui/scss/variables/_mixins.scss +24 -25
  90. package/src/wave-ui/scss/variables/_variables.scss +4 -151
  91. package/src/wave-ui/utils/colors.js +7 -4
  92. package/src/wave-ui/utils/config.js +5 -4
  93. package/src/wave-ui/utils/dynamic-css.js +42 -20
  94. package/src/wave-ui/utils/ripple.js +72 -0
  95. package/src/wave-ui/utils/wave-ripple-directive.js +40 -0
  96. package/dist/types/types/components/WApp.d.ts +0 -83
  97. package/src/wave-ui/components/w-app.vue +0 -24
  98. /package/dist/types/types/components/{WApp.js → WAutocomplete.js} +0 -0
@@ -9,7 +9,9 @@ ul.w-tree(:class="classes")
9
9
  :is="getTreeItemComponent(item)"
10
10
  v-bind="item.route && { [!$router || hasExternalLink(item) ? 'href' : 'to']: item.route }"
11
11
  @click="!disabled && !item.disabled && onLabelClick(item, $event)"
12
+ @pointerdown="onLabelPointerDown(item, $event)"
12
13
  @keydown="!disabled && !item.disabled && onLabelKeydown(item, $event)"
14
+ :class="itemLabelClasses(item)"
13
15
  :tabindex="getTreeItemTabindex(item)")
14
16
  //- @click.stop to not follow link if item is a link.
15
17
  w-button.w-tree__item-expand(
@@ -59,6 +61,7 @@ ul.w-tree(:class="classes")
59
61
  </template>
60
62
 
61
63
  <script>
64
+ import RippleMixin from '../mixins/ripple'
62
65
  import { consoleWarn } from '../utils/console'
63
66
  /**
64
67
  * @todo:
@@ -67,6 +70,9 @@ import { consoleWarn } from '../utils/console'
67
70
 
68
71
  export default {
69
72
  name: 'w-tree',
73
+
74
+ mixins: [RippleMixin],
75
+
70
76
  props: {
71
77
  modelValue: { type: [Object, Array] },
72
78
  data: { type: [Object, Array], required: true },
@@ -200,6 +206,16 @@ export default {
200
206
  return true // Just to chain instructions.
201
207
  },
202
208
 
209
+ isItemRippleActive (item) {
210
+ return this.rippleActive && this.selectable && !this.disabled && !item.disabled
211
+ },
212
+
213
+ itemLabelClasses (item) {
214
+ return {
215
+ 'w-ripple': this.isItemRippleActive(item)
216
+ }
217
+ },
218
+
203
219
  onLabelClick (item, e) {
204
220
  const route = item[this.itemRouteKey]
205
221
  if (route && this.$router && !this.hasExternalLink(item)) e.preventDefault()
@@ -212,6 +228,11 @@ export default {
212
228
  this.emitItemSelection(item, e) // Always emitting on click, but different event for selection.
213
229
  },
214
230
 
231
+ onLabelPointerDown (item, e) {
232
+ if (!this.isItemRippleActive(item) || e.target.closest?.('.w-tree__item-expand')) return
233
+ this.onRipple(e)
234
+ },
235
+
215
236
  onLabelKeydown (item, e) {
216
237
  // Keys: 13 enter, 32 space, 37 arrow left, 38 arrow up, 39 arrow right, 40 arrow down.
217
238
  if (!(e.metaKey || e.ctrlKey || e.altKey || e.shiftKey) && [13, 32, 37, 38, 39, 40].includes(e.keyCode)) {
@@ -339,16 +360,14 @@ export default {
339
360
  </script>
340
361
 
341
362
  <style lang="scss">
342
- $expand-icon-size: 20px;
343
-
344
363
  .w-tree {
364
+ --w-tree-expand-icon-size: 20px;
345
365
  margin: 0;
346
366
 
347
367
  // Tree items.
348
368
  // ------------------------------------------------------
349
369
  &__item {list-style-type: none;}
350
- &__item--branch {}
351
- &__item--leaf {margin-left: $base-increment * 5 + 2px;}
370
+ &__item--leaf {margin-left: calc(var(--w-base-increment) * 5 + 2px);}
352
371
  &--no-expand-button &__item--leaf {margin-left: 0;}
353
372
 
354
373
  // Tree item label.
@@ -364,21 +383,21 @@ $expand-icon-size: 20px;
364
383
  position: absolute;
365
384
  top: -1px;
366
385
  bottom: -1px;
367
- left: - $base-increment + 2px;
368
- right: - $base-increment - 2px;
369
- border-radius: $border-radius;
386
+ left: calc(var(--w-base-increment) * -1 + 2px);
387
+ right: calc(var(--w-base-increment) * -1 - 2px);
388
+ border-radius: var(--w-border-radius);
370
389
  }
371
- &:hover:before {background-color: $primary;opacity: 0.1;}
372
- &:focus-visible:before {background-color: $primary;opacity: 0.15;}
390
+ &:hover:before {background-color: var(--w-primary-color);opacity: 0.1;}
391
+ &:focus-visible:before {background-color: var(--w-primary-color);opacity: 0.15;}
373
392
  }
374
393
  &.w-tree--selectable &__item-label {cursor: pointer;}
375
394
  &.w-tree--selectable &__item--disabled &__item-label {cursor: auto;}
376
395
  &__item--leaf &__item-label:before {
377
- left: - $base-increment;
378
- right: - $base-increment;
396
+ left: calc(var(--w-base-increment) * -1);
397
+ right: calc(var(--w-base-increment) * -1);
379
398
  }
380
399
  &__item--selected > &__item-label:before {
381
- background-color: $primary;
400
+ background-color: var(--w-primary-color);
382
401
  opacity: 0.25;
383
402
  }
384
403
  &__item--disabled &__item-label {opacity: 0.5;}
@@ -388,21 +407,21 @@ $expand-icon-size: 20px;
388
407
 
389
408
  &__item--branch > &__item-label {cursor: pointer;}
390
409
  &__item--disabled > &__item-label {
391
- color: $disabled-color;
410
+ color: var(--w-disabled-color);
392
411
  cursor: not-allowed;
393
412
  -webkit-tap-highlight-color: transparent;
394
413
  }
395
414
  &__item--unexpandable > &__item-label {
396
- margin-left: $expand-icon-size + 2px;
415
+ margin-left: calc(var(--w-tree-expand-icon-size) + 2px);
397
416
  cursor: auto;
398
417
  }
399
418
  &--disabled &__item-label {cursor: auto;}
400
419
  &--disabled &__item--branch > &__item-label {opacity: 0.5;}
401
420
 
402
- &__item-icon {margin-right: $base-increment;}
421
+ &__item-icon {margin-right: var(--w-base-increment);}
403
422
 
404
423
  // Recursive children.
405
424
  // ------------------------------------------------------
406
- .w-tree {margin-left: $base-increment * 5;}
425
+ .w-tree {margin-left: calc(var(--w-base-increment) * 5);}
407
426
  }
408
427
  </style>
@@ -4,6 +4,7 @@ import { consoleWarn } from './utils/console'
4
4
  import { colorPalette, generateColorShades, flattenColors } from './utils/colors'
5
5
  import { injectColorsCSSInDOM, injectCSSInDOM } from './utils/dynamic-css'
6
6
  import { injectNotifManagerInDOM, NotificationManager } from './utils/notification-manager'
7
+ import { waveRippleDirective } from './utils/wave-ripple-directive'
7
8
  import './scss/index.scss'
8
9
 
9
10
  let mounted = false
@@ -90,7 +91,7 @@ export default class WaveUI {
90
91
  document.documentElement.setAttribute('data-theme', theme)
91
92
  document.head.querySelector('#wave-ui-colors')?.remove?.()
92
93
  const themeColors = this.config.colors[this.theme]
93
- injectColorsCSSInDOM(themeColors, this.config.css.colorShadeCssVariables)
94
+ injectColorsCSSInDOM(themeColors, colorPalette, this.config.css.colorShadeCssVariables)
94
95
  this.colors = flattenColors(themeColors, colorPalette)
95
96
  },
96
97
 
@@ -122,6 +123,7 @@ export default class WaveUI {
122
123
  window.addEventListener('scroll', f)
123
124
  }
124
125
  })
126
+ app.directive('waveRipple', waveRippleDirective)
125
127
 
126
128
  // Register a-la-carte components from the given list.
127
129
  const { components = {} } = options || {}
@@ -145,6 +147,14 @@ export default class WaveUI {
145
147
  const wApp = document.querySelector(config.on) || document.body
146
148
  wApp.classList.add('w-app')
147
149
 
150
+ // Add any custom app classes from config.
151
+ if (config.css.appClasses) {
152
+ const classesToAdd = typeof config.css.appClasses === 'string'
153
+ ? config.css.appClasses.split(' ').filter(c => c)
154
+ : config.css.appClasses
155
+ if (classesToAdd.length) wApp.classList.add(...classesToAdd)
156
+ }
157
+
148
158
  if (config.theme === 'auto') detectOSDarkMode($waveui) // Also switches the theme.
149
159
  else $waveui.switchTheme(config.theme, true)
150
160
 
@@ -2,6 +2,10 @@
2
2
  * A detachable element is an element that can be appended to another DOM node
3
3
  * (but keeping data-driven Vue DOM refreshes).
4
4
  * This mixin is used by w-tooltip & w-menu.
5
+ *
6
+ * Vue Teleport handles moving the floating content to the right DOM node.
7
+ * Event listeners are auto-attached to the activator slot's root element so
8
+ * callers no longer need the `template(#activator="{ on }") v-on="on"` pattern.
5
9
  */
6
10
 
7
11
  import { consoleWarn } from '../utils/console'
@@ -55,7 +59,11 @@ export default {
55
59
  // Set to true by computeDetachableCoords after positioning, false until then.
56
60
  // Components use this to bind visibility:hidden, so the element is never visible at the
57
61
  // wrong position before its coordinates are calculated.
58
- detachableReady: false
62
+ detachableReady: false,
63
+ // The Vue Teleport target. Stored as data (not computed) so it is resolved lazily at
64
+ // open()-time — after the DOM is committed — rather than during VNode creation where
65
+ // document.querySelector() may return null for elements that are part of the same render batch.
66
+ teleportTarget: null
59
67
  }),
60
68
 
61
69
  computed: {
@@ -74,7 +82,8 @@ export default {
74
82
 
75
83
  let target = this.appendTo || defaultTarget
76
84
  if (target === true) target = defaultTarget
77
- else if (this.appendTo === 'activator') target = this.$el.previousElementSibling || this.$el.nextElementSibling
85
+ // When appendTo is 'activator', teleport into the activator element itself.
86
+ else if (this.appendTo === 'activator') target = this.activatorEl
78
87
  else if (target && !['object', 'string'].includes(typeof target)) target = defaultTarget
79
88
  else if (typeof target === 'object' && !target.nodeType) {
80
89
  target = defaultTarget
@@ -111,7 +120,9 @@ export default {
111
120
  if (typeof document === 'undefined') return null
112
121
  return document.querySelector(this.activator)
113
122
  }
114
- return this.$el.nextElementSibling
123
+ // For slot-based activators the component root ($el) is a comment fragment anchor in
124
+ // Vue 3 multi-root components; nextElementSibling is the activator slot's first real element.
125
+ return this.$el?.nextElementSibling || null
115
126
  },
116
127
  set () {}
117
128
  },
@@ -154,6 +165,7 @@ export default {
154
165
  // next open starts hidden. Done here rather than on close() to let the leave animation play.
155
166
  onAfterLeave () {
156
167
  this.detachableReady = false
168
+ this.detachableEl = null
157
169
  },
158
170
 
159
171
  unbindActivatorDocEvents () {
@@ -166,6 +178,61 @@ export default {
166
178
  }
167
179
  },
168
180
 
181
+ /**
182
+ * Single delegating handler for auto-attached slot-activator DOM events.
183
+ * Reads the current activatorEventHandlers computed each invocation so that changes to
184
+ * `disable`, `showOnHover`, etc. are always reflected without re-attaching.
185
+ */
186
+ _handleActivatorEvent (e) {
187
+ const handler = this.activatorEventHandlers[e.type]
188
+ if (handler) handler(e)
189
+ },
190
+
191
+ /**
192
+ * Attach DOM event listeners directly to the activator slot's root element.
193
+ * Called once from mounted(); a single delegating handler covers all event types so we never
194
+ * need to re-attach when props like `disable` or `showOnHover` change.
195
+ * ! \ This function uses the DOM - NO SSR.
196
+ */
197
+ _attachActivatorListeners () {
198
+ if (typeof document === 'undefined') return
199
+ const el = this.activatorEl
200
+ if (!el) return
201
+
202
+ // Inspect the activator slot's first VNode for pre-declared event handlers.
203
+ // When the slot root already declares onClick / onMouseenter etc. — whether on a native
204
+ // element (w-select, w-autocomplete) or on a component (w-button @click="...") — the
205
+ // parent is managing that event itself. Skip auto-attaching the competing handler to avoid
206
+ // open/close races (both toggle() and the explicit handler firing on the same click).
207
+ // With the new API, the default slot is the activator (no #activator slot used).
208
+ let existingHandlers = {}
209
+ const activatorSlot = this.$slots.activator || this.$slots.default
210
+ if (activatorSlot) {
211
+ const vnodes = activatorSlot()
212
+ const firstVnode = vnodes?.[0]
213
+ existingHandlers = firstVnode?.props || {}
214
+ }
215
+
216
+ this._activatorDomEl = el
217
+ this._activatorAttachedEvents = []
218
+ ;['click', 'mouseenter', 'mouseleave', 'focus', 'blur'].forEach(evt => {
219
+ // Skip if the slot element already binds this event (camelCase Vue prop name: onClick etc.).
220
+ const vueProp = `on${evt.charAt(0).toUpperCase()}${evt.slice(1)}`
221
+ if (existingHandlers[vueProp]) return
222
+ el.addEventListener(evt, this._handleActivatorEvent)
223
+ this._activatorAttachedEvents.push(evt)
224
+ })
225
+ },
226
+
227
+ _detachActivatorListeners () {
228
+ if (!this._activatorDomEl) return
229
+ ;(this._activatorAttachedEvents || []).forEach(evt => {
230
+ this._activatorDomEl.removeEventListener(evt, this._handleActivatorEvent)
231
+ })
232
+ this._activatorDomEl = null
233
+ this._activatorAttachedEvents = []
234
+ },
235
+
169
236
  // ! \ This function uses the DOM - NO SSR (only trigger from beforeMount and later).
170
237
  async open (e) {
171
238
  if (this.disable) return
@@ -181,13 +248,22 @@ export default {
181
248
 
182
249
  // Hide before entering the DOM; handles rapid re-opens where detachableReady is still true.
183
250
  this.detachableReady = false
251
+
252
+ // Resolve the teleport target here, at open()-time, so the DOM is fully committed and
253
+ // detachableDefaultRoot() (for nested menus/tooltips) returns the correct element.
254
+ // Setting teleportTarget and detachableVisible in the same synchronous block lets Vue
255
+ // batch both into a single render — the content is never shown at the wrong location.
256
+ if (typeof document !== 'undefined') this.teleportTarget = this.appendToTarget
257
+
184
258
  this.detachableVisible = true
185
259
 
186
260
  // If the activator is external, there might be multiple,
187
261
  // so on open, the activator will be set to the event target.
188
262
  if (this.activator) this.activatorEl = e.target
189
263
 
190
- await this.insertInDOM()
264
+ // Wait for Vue Teleport to render the floating element into the target DOM node.
265
+ await this.$nextTick()
266
+ this.detachableEl = this.$refs.detachable?.$el || this.$refs.detachable
191
267
 
192
268
  if (this.minWidth === 'activator' && this.activatorEl) {
193
269
  this.activatorWidth = this.activatorEl.offsetWidth
@@ -361,32 +437,6 @@ export default {
361
437
  }
362
438
  },
363
439
 
364
- insertInDOM () {
365
- return new Promise(resolve => {
366
- this.$nextTick(() => {
367
- this.detachableEl = this.$refs.detachable?.$el || this.$refs.detachable
368
-
369
- // Move the tooltip/menu elsewhere in the DOM.
370
- if (this.detachableEl && this.appendToTarget) this.appendToTarget.appendChild(this.detachableEl)
371
- resolve()
372
- })
373
- })
374
- },
375
-
376
- removeFromDOM () {
377
- if (typeof document !== 'undefined') {
378
- document.removeEventListener('mousedown', this.onOutsideMousedown)
379
- }
380
- if (typeof window !== 'undefined') {
381
- window.removeEventListener('resize', this.onResize)
382
- }
383
- if (this.detachableEl?.parentNode) {
384
- this.detachableVisible = false
385
- this.detachableEl.remove()
386
- this.detachableEl = null
387
- }
388
- },
389
-
390
440
  // If the activator is external, add event listeners to the document and check the target is
391
441
  // the activator when toggling.
392
442
  // This way, the activator can be a future DOM element, that is not yet in the DOM.
@@ -401,8 +451,8 @@ export default {
401
451
  eventName = eventName.replace('mouseenter', 'mouseover').replace('mouseleave', 'mouseout')
402
452
  const handlerWrap = e => {
403
453
  // The activator can be a DOM string selector a ref or a DOM node.
404
- if (activatorIsString && e.target?.matches && e.target.matches(this.activator)) handler(e)
405
- else if (e.target === this.activatorEl || this.activatorEl.contains(e.target)) handler(e)
454
+ if (activatorIsString && e.target?.matches?.(this.activator)) handler(e)
455
+ else if (e.target === this.activatorEl || this.activatorEl?.contains(e.target)) handler(e)
406
456
  }
407
457
  document.addEventListener(eventName, handlerWrap)
408
458
  // The event listeners handlers have to be removed the exact same way they have been attached.
@@ -414,14 +464,18 @@ export default {
414
464
  },
415
465
 
416
466
  mounted () {
417
- // If the activator is external.
418
- if (this.activator) this.bindActivatorEvents()
419
-
420
- // If the activator seems to be undefined, it is probably a DOM node or Vue ref,
421
- // so check it on nextTick.
467
+ if (this.activator) {
468
+ // External activator: attach via document-level delegation.
469
+ this.bindActivatorEvents()
470
+ }
422
471
  else {
472
+ // Slot-based activator: auto-attach DOM listeners to the slot's root element on next tick
473
+ // so the slot content is guaranteed to be in the DOM.
423
474
  this.$nextTick(() => {
475
+ // Re-check activator prop (might have resolved from a Vue ref after the tick).
424
476
  if (this.activator) this.bindActivatorEvents()
477
+ else this._attachActivatorListeners()
478
+
425
479
  if (this.modelValue && !this.disable) this.open({ target: this.activatorEl })
426
480
  })
427
481
  }
@@ -438,10 +492,8 @@ export default {
438
492
  unmounted () {
439
493
  this.close()
440
494
 
441
- this.removeFromDOM()
442
-
443
- // Remove the event listeners the exact same way they have been defined.
444
- // Fixes issues on hot-reloading.
495
+ // Clean up slot-activator DOM listeners and external-activator document listeners.
496
+ this._detachActivatorListeners()
445
497
  this.unbindActivatorDocEvents()
446
498
  },
447
499
 
@@ -451,6 +503,8 @@ export default {
451
503
  this.unbindActivatorDocEvents()
452
504
  if (!disabled) this.bindActivatorEvents()
453
505
  }
506
+ // For slot-based activators, _handleActivatorEvent always reads the current
507
+ // activatorEventHandlers computed which already respects `disable`, so no re-attach needed.
454
508
  if (disabled) this.close()
455
509
  else if (this.modelValue) this.open({ target: this.activatorEl })
456
510
  },
@@ -461,9 +515,10 @@ export default {
461
515
  else if (!bool) this.close()
462
516
  }
463
517
  },
518
+
519
+ // Keep teleportTarget in sync when the appendTo prop changes at runtime.
464
520
  appendTo () {
465
- this.removeFromDOM()
466
- this.insertInDOM()
521
+ if (typeof document !== 'undefined') this.teleportTarget = this.appendToTarget
467
522
  }
468
523
  }
469
524
  }
@@ -0,0 +1,39 @@
1
+ import { applyRipple, isRippleEnabled } from '../utils/ripple'
2
+
3
+ export default {
4
+ inject: {
5
+ $waveui: { from: '$waveui', default: () => ({ config: { ripple: true } }) }
6
+ },
7
+
8
+ props: {
9
+ /** Set to `true` to disable the ripple for this component only. */
10
+ noRipple: { type: Boolean, default: undefined }
11
+ },
12
+
13
+ computed: {
14
+ rippleActive () {
15
+ return isRippleEnabled(this.noRipple ? false : undefined, this.$waveui)
16
+ }
17
+ },
18
+
19
+ methods: {
20
+ /**
21
+ * Resolve host for applyRipple. Vue (and some browsers) can leave `event.currentTarget` null
22
+ * on delegated handlers, which would otherwise skip the ripple entirely.
23
+ */
24
+ onRipple (e, hostEl, options) {
25
+ if (!this.rippleActive || !e) return
26
+ let host = hostEl ?? e.currentTarget
27
+ if (!host?.getBoundingClientRect) {
28
+ const raw = e.target
29
+ const el = raw?.nodeType === 1 ? raw : raw?.parentElement
30
+ host = el?.closest?.('.w-ripple') ?? null
31
+ }
32
+ if (!host?.getBoundingClientRect && this.$el instanceof Element && this.$el.classList?.contains('w-ripple')) {
33
+ host = this.$el
34
+ }
35
+ if (!host?.getBoundingClientRect) return
36
+ applyRipple(host, e, options)
37
+ }
38
+ }
39
+ }
@@ -1,11 +1,43 @@
1
- @use "sass:map";
2
1
  @use "variables" as *;
3
2
 
4
3
  // The CSS variables are used in the dynamic-css.js file in order to reuse the same SCSS
5
4
  // variable presets.
6
5
  :root {
7
- --w-base-increment: #{$base-increment};
6
+ --w-base-font-size: 14px;
7
+ // Keeps spacing in sync when only --w-base-font-size is overridden (matches former Sass rounding).
8
+ --w-base-increment: round(nearest, calc(var(--w-base-font-size) / 4), 1px);
9
+ // Sass-only: must match $css-scope in _layout.scss and what dynamic-css reads for injected rules.
8
10
  --w-css-scope: #{$css-scope};
11
+ --w-layout-padding: 16px;
12
+ --w-border-radius: 4px;
13
+ --w-border-width: 1px;
14
+ --w-border-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 12%, transparent)};
15
+ --w-border: var(--w-border-width) solid var(--w-border-color);
16
+ --w-transition-duration: 0.25s;
17
+ --w-transition-duration-fast: 0.15s;
18
+ --w-transition-timing-fast-out-slow-in: cubic-bezier(0.4, 0, 0.2, 1);
19
+ --w-box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2),
20
+ 0 2px 2px 0 rgba(0, 0, 0, 0.15),
21
+ 0 1px 5px 0 rgba(0, 0, 0, 0.15);
22
+ --w-form-field-height: round(nearest, calc(2 * var(--w-base-font-size)), 1px);
23
+ // Even px step (same as former 2 * round(1.3 * $base-font-size / 2)).
24
+ --w-small-form-el-size: round(nearest, calc(1.3 * var(--w-base-font-size)), 2px);
25
+ --w-scrollbar-size: 8px;
26
+
27
+ @for $i from -6 through -1 {
28
+ --w-shadow-n#{-1 * $i}: 0 0 round(nearest, calc(#{$i} * var(--w-base-increment)), 1px) rgba(0, 0, 0, max(0.15, calc(0.15 * #{-$i} / 2))) inset;
29
+ }
30
+ --w-shadow-0: none;
31
+ @for $i from 1 through 6 {
32
+ --w-shadow-#{$i}: 0 0 1px rgba(0, 0, 0, 0.1),
33
+ round(nearest, calc(var(--w-base-increment) * #{$i} / 4), 1px) round(nearest, calc(var(--w-base-increment) * #{$i} / 4), 1px) round(nearest, calc(#{$i} * var(--w-base-increment)), 1px) rgba(0, 0, 0, max(0.15, calc(0.15 * #{$i} / 2)));
34
+ }
35
+
36
+ --w-base-color-muted: #{color-mix(in srgb, var(--w-base-color) 70%, transparent)};
37
+ --w-overlay-scrim-color: #{color-mix(in srgb, #000 30%, transparent)};
38
+ --w-surface-hover-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 5%, transparent)};
39
+ --w-surface-active-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 8%, transparent)};
40
+ --w-surface-selected-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 15%, transparent)};
9
41
  --w-contrast-bg-o025-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 2.5%, transparent)};
10
42
  --w-contrast-bg-o05-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 5%, transparent)};
11
43
  --w-contrast-bg-o1-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 10%, transparent)};
@@ -18,26 +50,59 @@
18
50
  --w-contrast-bg-o8-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 80%, transparent)};
19
51
  --w-contrast-bg-o9-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 90%, transparent)};
20
52
 
53
+ --w-detachable-bg-color: var(--w-base-bg-color);
54
+ --w-detachable-color: var(--w-base-color);
55
+ --w-confirm-bg-color: var(--w-detachable-bg-color);
56
+ --w-confirm-color: var(--w-detachable-color);
57
+ --w-dialog-bg-color: var(--w-base-bg-color);
58
+ --w-divider-color: var(--w-border-color);
59
+ --w-drawer-max-size: 380px;
60
+ --w-drawer-bg-color: var(--w-base-bg-color);
61
+ --w-menu-bg-color: var(--w-detachable-bg-color);
62
+ --w-menu-color: var(--w-detachable-color);
63
+ --w-progress-bg-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 15%, transparent)};
64
+ --w-rating-bg-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 25%, transparent)};
65
+ --w-slider-height: var(--w-base-increment);
66
+ --w-slider-track-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 15%, transparent)};
67
+ --w-slider-thumb-button-bg-color: var(--w-base-bg-color);
68
+ --w-slider-thumb-label-bg-color: var(--w-base-bg-color);
69
+ --w-slider-thumb-label-color: #{color-mix(in srgb, var(--w-base-color) 75%, transparent)};
70
+ --w-slider-step-label-bg-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 20%, transparent)};
71
+ --w-slider-step-label-color: #{color-mix(in srgb, var(--w-base-color) 50%, transparent)};
72
+ --w-switch-inactive-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 25%, transparent)};
73
+ --w-switch-thumb-color: var(--w-base-bg-color);
74
+ --w-table-tr-odd-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 2%, transparent)};
75
+ --w-table-tr-hover-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 5%, transparent)};
76
+ --w-table-color: #{color-mix(in srgb, var(--w-contrast-color) 70%, transparent)};
77
+ --w-textarea-line-height: 1.2;
78
+ --w-timeline-bullet-color: var(--w-base-bg-color);
79
+ --w-timeline-bg-color: #{color-mix(in srgb, var(--w-contrast-bg-color) 25%, transparent)};
80
+ --w-toolbar-max-size: 380px;
81
+ --w-toolbar-bg-color: var(--w-base-bg-color);
82
+ --w-tooltip-bg-color: var(--w-detachable-bg-color);
83
+ --w-tooltip-color: var(--w-detachable-color);
84
+ --w-tooltip-border-color: var(--w-border-color);
85
+
21
86
  background-color: var(--w-base-bg-color);
22
87
  color: var(--w-base-color);
23
88
  }
24
89
 
25
90
  :root[data-theme="light"] {
26
- --w-base-bg-color: #{map.get($theme-light, 'base-bg-color')};
27
- --w-base-color: #{map.get($theme-light, 'base-color')};
28
- --w-contrast-bg-color: #{map.get($theme-light, 'contrast-bg-color')};
29
- --w-contrast-color: #{map.get($theme-light, 'contrast-color')};
30
- --w-caption-color: #{map.get($theme-light, 'caption-color')};
31
- --w-disabled-color: #{map.get($theme-light, 'disabled-color')};
91
+ --w-base-bg-color: #fff;
92
+ --w-base-color: #000;
93
+ --w-contrast-bg-color: #000;
94
+ --w-contrast-color: #fff;
95
+ --w-caption-color: #a0a0a0;
96
+ --w-disabled-color: #ccc;
32
97
  }
33
98
 
34
99
  :root[data-theme="dark"] {
35
- --w-base-bg-color: #{map.get($theme-dark, 'base-bg-color')};
36
- --w-base-color: #{map.get($theme-dark, 'base-color')};
37
- --w-contrast-bg-color: #{map.get($theme-dark, 'contrast-bg-color')};
38
- --w-contrast-color: #{map.get($theme-dark, 'contrast-color')};
39
- --w-caption-color: #{map.get($theme-dark, 'caption-color')};
40
- --w-disabled-color: #{map.get($theme-dark, 'disabled-color')};
100
+ --w-base-bg-color: #222;
101
+ --w-base-color: #fff;
102
+ --w-contrast-bg-color: #fff;
103
+ --w-contrast-color: #000;
104
+ --w-caption-color: #6e6e6e;
105
+ --w-disabled-color: #4a4a4a;
41
106
  }
42
107
 
43
108
  * {
@@ -72,13 +137,13 @@ a {text-decoration: none;}
72
137
  }
73
138
 
74
139
  .w-main {
75
- padding-left: 3 * $base-increment;
76
- padding-right: 3 * $base-increment;
140
+ padding-left: calc(3 * var(--w-base-increment));
141
+ padding-right: calc(3 * var(--w-base-increment));
77
142
  }
78
143
 
79
144
  // Structure classes.
80
145
  // ----------------------------------------------
81
146
  .content-wrap {
82
147
  position: relative;
83
- padding: $layout-padding;
148
+ padding: var(--w-layout-padding);
84
149
  }
@@ -1,4 +1,3 @@
1
- @use 'sass:color';
2
1
  @use 'variables' as *;
3
2
 
4
3
  #{$css-scope} {
@@ -29,82 +28,14 @@
29
28
  // For each color, create a [color] and a [color]--bg associated classes,
30
29
  // + 6 shades lighter and 6 shades darker.
31
30
  @each $label, $color in $colors {
32
- .#{$label}--bg {background-color: $color;}
33
- .#{$label} {color: $color;}
31
+ .#{$label}--bg {background-color: var(--w-#{$label}-color);}
32
+ .#{$label} {color: var(--w-#{$label}-color);}
34
33
 
35
34
  @for $i from 1 through 6 {
36
- $light-increment: 7.5;
37
- $light-offset: 0;
38
- $dark-increment: 6.2;
39
- // Some color shades need bigger or smaller increments to end up with the same scale.
40
- @if $label == 'deep-orange' {
41
- $light-increment: 6.4;
42
- }
43
- @if $label == 'orange' {
44
- }
45
- @else if $label == 'green' {
46
- $light-increment: 7.6;
47
- $dark-increment: 5.7;
48
- }
49
- @else if $label == 'amber' {
50
- }
51
- @else if $label == 'pink' {
52
- $light-increment: 6.7;
53
- $light-offset: -4;
54
- }
55
- @else if $label == 'red' {
56
- $light-increment: 6.5;
57
- $light-offset: -1;
58
- }
59
- @else if $label == 'indigo' {
60
- $light-increment: 8;
61
- $dark-increment: 5.7;
62
- }
63
- @else if $label == 'deep-purple' {
64
- $light-increment: 8;
65
- $dark-increment: 5.7;
66
- }
67
- @else if $label == 'light-blue' {
68
- $light-increment: 7.8;
69
- }
70
- @else if $label == 'light-green' {
71
- $light-increment: 6;
72
- $light-offset: -5;
73
- }
74
- @else if $label == 'lime' {
75
- $light-increment: 6.2;
76
- $light-offset: -6;
77
- }
78
- @else if $label == 'yellow' {
79
- $light-increment: 5.5;
80
- $light-offset: -8;
81
- }
82
- @else if $label == 'purple' {
83
- $light-increment: 6.5;
84
- $light-offset: -8.5;
85
- }
86
- @else if $label == 'cyan' {
87
- $light-increment: 9.4;
88
- $light-offset: 6.5;
89
- $dark-increment: 5.7;
90
- }
91
- @else if $label == 'teal' {
92
- $light-increment: 9.6;
93
- $light-offset: 5;
94
- $dark-increment: 5.4;
95
- }
96
- @else if $label == 'blue' {
97
- $light-increment: 6.8;
98
- $dark-increment: 6.8;
99
- }
100
- @else if $label == 'brown' {
101
- $light-increment: 8.8;
102
- $dark-increment: 5;
103
- }
104
- .#{$label}-light#{$i}--bg {background-color: color.adjust($color, $lightness: $light-increment * $i * 1% - $light-offset);}
105
- .#{$label}-light#{$i} {color: color.adjust($color, $lightness: $light-increment * $i * 1% - $light-offset);}
106
- .#{$label}-dark#{$i}--bg {background-color: color.adjust($color, $lightness: - $dark-increment * $i * 1%);}
107
- .#{$label}-dark#{$i} {color: color.adjust($color, $lightness: - $dark-increment * $i * 1%);}
35
+ .#{$label}-light#{$i}--bg {background-color: var(--w-#{$label}-light#{$i}-color);}
36
+ .#{$label}-light#{$i} {color: var(--w-#{$label}-light#{$i}-color);}
37
+ .#{$label}-dark#{$i}--bg {background-color: var(--w-#{$label}-dark#{$i}-color);}
38
+ .#{$label}-dark#{$i} {color: var(--w-#{$label}-dark#{$i}-color);}
108
39
  }
109
40
  }
110
41