@redseed/redseed-ui-vue3 10.3.1 → 10.3.3
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,5 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { ref, watch, watchEffect, nextTick, onMounted, onBeforeUnmount } from 'vue'
|
|
2
|
+
import { ref, shallowRef, watch, watchEffect, nextTick, onMounted, onBeforeUnmount } from 'vue'
|
|
3
3
|
import { ChevronDownIcon } from '@heroicons/vue/24/outline'
|
|
4
4
|
import { ButtonTertiary } from '../Button'
|
|
5
5
|
import Icon from '../Icon/Icon.vue'
|
|
@@ -50,6 +50,8 @@ async function setContentMaxHeight() {
|
|
|
50
50
|
|
|
51
51
|
await nextTick()
|
|
52
52
|
|
|
53
|
+
if (!contentRef.value) return
|
|
54
|
+
|
|
53
55
|
contentRef.value.style.minHeight = contentRef.value.scrollHeight + 'px'
|
|
54
56
|
}
|
|
55
57
|
|
|
@@ -58,6 +60,8 @@ async function resetContentMaxHeight() {
|
|
|
58
60
|
|
|
59
61
|
await nextTick()
|
|
60
62
|
|
|
63
|
+
if (!contentRef.value) return
|
|
64
|
+
|
|
61
65
|
contentRef.value.style.removeProperty('min-height')
|
|
62
66
|
}
|
|
63
67
|
|
|
@@ -88,7 +92,7 @@ watch(isOpen, (open, wasOpen) => {
|
|
|
88
92
|
}, { flush: 'sync' })
|
|
89
93
|
|
|
90
94
|
/**
|
|
91
|
-
*
|
|
95
|
+
* Each teleport target's current element, or null while it is absent.
|
|
92
96
|
*
|
|
93
97
|
* Re-checked rather than latched. These used to only ever flip true, and the
|
|
94
98
|
* observer disconnected once both had been found — so a consumer that removed a
|
|
@@ -96,15 +100,23 @@ watch(isOpen, (open, wasOpen) => {
|
|
|
96
100
|
* `SectionHeader` does exactly that: it width-gates the slots a target is
|
|
97
101
|
* mounted into, so narrowing a header past 640px took the trigger away and the
|
|
98
102
|
* section could no longer be opened. See #372.
|
|
103
|
+
*
|
|
104
|
+
* The element itself goes to `to`, not an `#id` selector. Vue resolves a
|
|
105
|
+
* selector once, when the Teleport mounts, and never again when only
|
|
106
|
+
* `disabled` flips. A Disclosure mounted inside a subtree that is not yet in the
|
|
107
|
+
* document resolved null there, and enabling it then threw "Cannot read
|
|
108
|
+
* properties of null (reading 'insertBefore')" and wedged the whole page. A
|
|
109
|
+
* changed `to` is re-resolved, so a target that appears late or is re-created
|
|
110
|
+
* gets the content moved into the live element.
|
|
99
111
|
*/
|
|
100
|
-
const
|
|
101
|
-
const
|
|
112
|
+
const triggerTarget = shallowRef(null)
|
|
113
|
+
const contentTarget = shallowRef(null)
|
|
102
114
|
|
|
103
115
|
let observer = null
|
|
104
116
|
|
|
105
117
|
function setTeleport() {
|
|
106
|
-
|
|
107
|
-
|
|
118
|
+
triggerTarget.value = document.getElementById(triggerId)
|
|
119
|
+
contentTarget.value = document.getElementById(contentId)
|
|
108
120
|
}
|
|
109
121
|
|
|
110
122
|
onMounted(() => {
|
|
@@ -149,8 +161,8 @@ onBeforeUnmount(() => {
|
|
|
149
161
|
Vue's own `disabled` keeps this as one definition rendered in one of two
|
|
150
162
|
places, rather than two copies that can drift.
|
|
151
163
|
-->
|
|
152
|
-
<Teleport :to="
|
|
153
|
-
:disabled="!
|
|
164
|
+
<Teleport :to="triggerTarget"
|
|
165
|
+
:disabled="!triggerTarget"
|
|
154
166
|
>
|
|
155
167
|
<slot name="trigger"
|
|
156
168
|
:handleTrigger="handleTrigger"
|
|
@@ -184,8 +196,8 @@ onBeforeUnmount(() => {
|
|
|
184
196
|
</Teleport>
|
|
185
197
|
|
|
186
198
|
<!-- Same reasoning as the trigger above: in place rather than nowhere. -->
|
|
187
|
-
<Teleport :to="
|
|
188
|
-
:disabled="!
|
|
199
|
+
<Teleport :to="contentTarget"
|
|
200
|
+
:disabled="!contentTarget"
|
|
189
201
|
>
|
|
190
202
|
<div ref="contentRef"
|
|
191
203
|
:class="[
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { ref, computed, watch, watchEffect, useSlots
|
|
2
|
+
import { ref, computed, watch, watchEffect, useSlots } from 'vue'
|
|
3
3
|
import Icon from '../Icon/Icon.vue'
|
|
4
4
|
import { XMarkIcon } from '@heroicons/vue/24/outline'
|
|
5
|
+
import { rendersContent } from '../../helpers/slots'
|
|
5
6
|
|
|
6
7
|
const props = defineProps({
|
|
7
8
|
closed: {
|
|
@@ -81,40 +82,6 @@ watch(() => props.closed, (value) => {
|
|
|
81
82
|
isClosed.value = value
|
|
82
83
|
})
|
|
83
84
|
|
|
84
|
-
/**
|
|
85
|
-
* A Fragment is a wrapper, so its own presence answers nothing — only what is
|
|
86
|
-
* inside it does. Hence the recursion: a `v-for` compiles to a Fragment and
|
|
87
|
-
* leaves a comment placeholder for each hidden item, so counting children
|
|
88
|
-
* reported content for a list whose every action was filtered out (the usual
|
|
89
|
-
* shape for permission-gated actions), and a nested `<template>` puts another
|
|
90
|
-
* Fragment inside that one.
|
|
91
|
-
*
|
|
92
|
-
* `Array.isArray` rather than recursing straight into `children`, which is a raw
|
|
93
|
-
* string on a Fragment built by hand as `h(Fragment, null, 'text')`. Vue's own
|
|
94
|
-
* `ensureValidVNode` assumes the array and throws on that shape, so answering
|
|
95
|
-
* "renders" here only hands it a slot it cannot render; treating it as empty
|
|
96
|
-
* drops the row instead, which is the quieter of the two failures. Compiled
|
|
97
|
-
* templates always produce the array.
|
|
98
|
-
*/
|
|
99
|
-
function rendersNode(node) {
|
|
100
|
-
if (node.type === Comment) return false
|
|
101
|
-
if (node.type === Fragment) return Array.isArray(node.children) && node.children.some(rendersNode)
|
|
102
|
-
|
|
103
|
-
return true
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Whether a slot renders anything at all. Slot *presence* is not the same
|
|
108
|
-
* question: a slot holding only a falsy `v-if` is still a truthy slot function
|
|
109
|
-
* that renders a comment placeholder, which would put an empty padded row under
|
|
110
|
-
* the message.
|
|
111
|
-
*/
|
|
112
|
-
function rendersContent(slot) {
|
|
113
|
-
const nodes = slot?.()
|
|
114
|
-
|
|
115
|
-
return Boolean(nodes) && nodes.some(rendersNode)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
85
|
/**
|
|
119
86
|
* Announce the box as a live region so flash / MOTD content reaches assistive
|
|
120
87
|
* tech (WCAG 4.1.3 Status Messages). `alert` is assertive — reserved for errors
|
|
@@ -195,7 +162,15 @@ function close() {
|
|
|
195
162
|
text on a coloured tint is a contrast problem, and the action is a way
|
|
196
163
|
out of the message rather than a restatement of its severity.
|
|
197
164
|
-->
|
|
198
|
-
|
|
165
|
+
<!--
|
|
166
|
+
The scope is forwarded, and that is the whole of #331. `#actions` is declared
|
|
167
|
+
scoped below — `<slot name="actions" :close="close">` — so a consumer writing
|
|
168
|
+
the documented `<template #actions="{ close }">` compiles to a function that
|
|
169
|
+
destructures its only parameter. Probing it bare passed `undefined` and threw
|
|
170
|
+
`Cannot destructure property 'close' of 'undefined'`, taking the whole
|
|
171
|
+
MessageBox down. Shipped in 8.66.0.
|
|
172
|
+
-->
|
|
173
|
+
<div v-if="hasDismiss || rendersContent($slots.actions, { close })" class="rsui-message-box__actions">
|
|
199
174
|
<button v-if="hasDismiss"
|
|
200
175
|
type="button"
|
|
201
176
|
class="rsui-message-box__dismiss"
|
package/src/helpers/slots.js
CHANGED
|
@@ -52,7 +52,15 @@ export function rendersContent(slot, scope) {
|
|
|
52
52
|
* Comment vnodes are what a falsy `v-if` leaves behind. Fragments wrap lists and
|
|
53
53
|
* `<template v-if>` blocks, so a truthy wrapper around a falsy child is a
|
|
54
54
|
* Fragment whose only child is a Comment — counting length would treat that as
|
|
55
|
-
* content.
|
|
55
|
+
* content. A `v-for` over a filtered list is the usual shape, and a nested
|
|
56
|
+
* `<template>` puts another Fragment inside that one.
|
|
57
|
+
*
|
|
58
|
+
* `Array.isArray` rather than recursing straight into `children`, which is a raw
|
|
59
|
+
* string on a Fragment built by hand as `h(Fragment, null, 'text')`. Vue's own
|
|
60
|
+
* `ensureValidVNode` assumes the array and throws on that shape, so answering
|
|
61
|
+
* "renders" there only hands it a slot it cannot render; treating it as empty
|
|
62
|
+
* drops the region instead, which is the quieter of the two failures. Compiled
|
|
63
|
+
* templates always produce the array.
|
|
56
64
|
*/
|
|
57
65
|
function rendersNode(node) {
|
|
58
66
|
if (node.type === Comment) return false
|