@redseed/redseed-ui-vue3 10.5.0 → 11.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redseed/redseed-ui-vue3",
3
- "version": "10.5.0",
3
+ "version": "11.0.0",
4
4
  "description": "RedSeed UI Vue 3 components",
5
5
  "main": "index.js",
6
6
  "repository": "https://github.com/redseedtraining/redseed-ui",
@@ -29,7 +29,6 @@ const props = defineProps({
29
29
 
30
30
  const emit = defineEmits(['click'])
31
31
 
32
- const triggerId = _.uniqueId('disclosure-trigger-')
33
32
  const contentId = _.uniqueId('disclosure-content-')
34
33
 
35
34
  const isOpen = ref(props.open)
@@ -139,14 +138,15 @@ watch(isOpen, (open, wasOpen) => {
139
138
  }, { flush: 'sync' })
140
139
 
141
140
  /**
142
- * Each teleport target's current element, or null while it is absent.
141
+ * The content teleport target's current element, or null while it is absent.
143
142
  *
144
143
  * Re-checked rather than latched. These used to only ever flip true, and the
145
144
  * observer disconnected once both had been found — so a consumer that removed a
146
145
  * target left a Teleport mounted against nothing and the trigger vanished.
147
- * `SectionHeader` does exactly that: it width-gates the slots a target is
148
- * mounted into, so narrowing a header past 640px took the trigger away and the
149
- * section could no longer be opened. See #372.
146
+ * `SectionHeader` did exactly that: it width-gated the slots a target was mounted
147
+ * into, so narrowing a header past 640px took the trigger away and the section
148
+ * could no longer be opened (#372). The trigger no longer teleports at all (#408);
149
+ * the content still does, because 27 consumers still place a content pad.
150
150
  *
151
151
  * The element itself goes to `to`, not an `#id` selector. Vue resolves a
152
152
  * selector once, when the Teleport mounts, and never again when only
@@ -156,13 +156,11 @@ watch(isOpen, (open, wasOpen) => {
156
156
  * changed `to` is re-resolved, so a target that appears late or is re-created
157
157
  * gets the content moved into the live element.
158
158
  */
159
- const triggerTarget = shallowRef(null)
160
159
  const contentTarget = shallowRef(null)
161
160
 
162
161
  let observer = null
163
162
 
164
163
  function setTeleport() {
165
- triggerTarget.value = document.getElementById(triggerId)
166
164
  contentTarget.value = document.getElementById(contentId)
167
165
  }
168
166
 
@@ -189,39 +187,41 @@ onBeforeUnmount(() => {
189
187
  <div class="rsui-disclosure">
190
188
  <slot name="child"
191
189
  :isOpen="isOpen"
192
- :triggerId="triggerId"
193
190
  :contentId="contentId"
194
191
  :handleTrigger="handleTrigger"
195
192
  ></slot>
196
193
  </div>
197
194
 
198
195
  <!--
199
- `disabled` rather than `v-if`, so the trigger renders in place when the
200
- consumer supplies no target instead of not rendering at all.
196
+ Rendered IN PLACE. There is no trigger Teleport any more.
201
197
 
202
- There used to be no second branch: `<Teleport v-if="canTeleportTrigger">`
203
- and nothing else, so a missing target meant no trigger ANYWHERE rather
204
- than one in a less good place. `SectionHeader` width-gates the slots a
205
- target is mounted into, so below 640px of its OWN width — container, not
206
- viewport — a section simply could not be opened. See #372.
198
+ It used to be teleported into an empty `<div :id="triggerId">` the consumer left
199
+ somewhere in their own markup, and every failure this component has had came from
200
+ that handoff — a pad in a width-gated slot (#372), a pad removed by
201
+ `:showMoreActions="false"` (#407), banned utility classes to sit a pad beside a
202
+ title. `collapsible` on SectionHeader and CardHeader replaced it: the header asks
203
+ whether it is inside a Disclosure and renders the trigger itself.
207
204
 
208
- Vue's own `disabled` keeps this as one definition rendered in one of two
209
- places, rather than two copies that can drift.
205
+ With no consumer placing a trigger pad, the Teleport had nothing to do — it was
206
+ already `:disabled` for every one of them, which renders exactly here. So this is
207
+ not a behaviour change, it is the removal of a branch nothing took. See #408.
208
+
209
+ A consumer wanting the trigger somewhere specific puts a `collapsible` header
210
+ there, or writes `<DisclosureTrigger>` where they want it.
210
211
  -->
211
- <Teleport v-if="rendersOwnTrigger"
212
- :to="triggerTarget"
213
- :disabled="!triggerTarget"
214
- >
212
+ <!--
213
+ Gated as a whole, not just the default content. A header that claims the trigger
214
+ (`collapsible`) suppresses this ENTIRELY, so a consumer supplying a custom `#trigger`
215
+ alongside a collapsible header gets one control rather than two. That is what the
216
+ Teleport's own `v-if` used to do, and dropping it to the default slot content alone
217
+ would have quietly changed it.
218
+ -->
219
+ <template v-if="rendersOwnTrigger">
215
220
  <slot name="trigger"
216
221
  :handleTrigger="handleTrigger"
217
222
  :isOpen="isOpen"
218
223
  :contentId="contentId"
219
224
  >
220
- <!--
221
- The markup itself moved to DisclosureTrigger so a header can render
222
- the same button in its own title row. Rendered here it is unchanged:
223
- ButtonSlot treats an unset size prop and a false one alike.
224
- -->
225
225
  <DisclosureTrigger
226
226
  :sm="$attrs.sm"
227
227
  :md="$attrs.md"
@@ -231,7 +231,7 @@ onBeforeUnmount(() => {
231
231
  :full="$attrs.full"
232
232
  />
233
233
  </slot>
234
- </Teleport>
234
+ </template>
235
235
 
236
236
  <!-- Same reasoning as the trigger above: in place rather than nowhere. -->
237
237
  <Teleport :to="contentTarget"
@@ -72,19 +72,27 @@ const props = defineProps({
72
72
  default: false,
73
73
  },
74
74
  /**
75
- * Keep the badge on the title line on a narrow header.
75
+ * Drop the badge onto its own line on a narrow header.
76
76
  *
77
- * `--stacked` puts `basis-full` on the title text, so below 640px the badge does
78
- * not wrap when it runs out of room — it ALWAYS starts a new line. A group header
79
- * carrying a count then stacks three rows above the first card, which is the second
80
- * of the two reasons TeamCoachingIndex gives for hand-placing its badge inside the
81
- * title rather than using `#badge` at all.
77
+ * OPT-IN. It used to be the default, with `inlineBadge` to turn it off, and that was the
78
+ * wrong way round — BREAKING, hence the major.
82
79
  *
83
- * Separate from `collapsible` on purpose: a collapsible section does not necessarily
84
- * want an inline badge, and a badge-carrying section need not collapse. Default
85
- * false, so no existing header moves. See #407.
80
+ * The slot is a count. The Section standards say so outright ("a badge in a section
81
+ * header says how many, not how it is going"), and 12 of 17 lms1 call sites are counts:
82
+ * `filteredCount`, `categoryGroup.count`, group counts. A number on its own line is
83
+ * severed from the thing it counts.
84
+ *
85
+ * `PageHeader` already settled the layout: its `__status` badge sits in
86
+ * `__title-text-container` with `items-center` and has no narrow branch at all.
87
+ *
88
+ * Measured before the flip: a 25px badge with 211px of clear room beside it still went
89
+ * to row two, costing 26px of header height for nothing. Of four test cases only one had
90
+ * a title long enough to need the wrap.
91
+ *
92
+ * Kept rather than deleted, because stacking is defensible when it is CHOSEN — a long
93
+ * label badge, rather than a count, may genuinely want its own line. See #407.
86
94
  */
87
- inlineBadge: {
95
+ stackBadge: {
88
96
  type: Boolean,
89
97
  default: false,
90
98
  },
@@ -213,6 +221,11 @@ const titleTag = computed(() => HEADING_LEVELS.includes(String(props.headingLeve
213
221
  {
214
222
  'rsui-section-header--divider': showDivider,
215
223
  'rsui-section-header--clickable': isClickable,
224
+ // Direction driven by the SAME measurement as everything else in here.
225
+ // It used to come from `md:` — a viewport query at 768 — while the icon
226
+ // gate, the actions swap and the badge stacking all read the container at
227
+ // 640, so a narrow column on a wide screen got both layouts at once. #420.
228
+ 'rsui-section-header--narrow': !responsiveWidth.specific,
216
229
  }
217
230
  ]"
218
231
  :role="isClickable ? 'button' : undefined"
@@ -222,6 +235,27 @@ const titleTag = computed(() => HEADING_LEVELS.includes(String(props.headingLeve
222
235
  >
223
236
  <div class="rsui-section-header__header">
224
237
 
238
+ <!--
239
+ The chevron takes the LEADING position, beside the text block rather than
240
+ inside the title — the same spot `#icon` occupies, and the same shape
241
+ `CardHeader` and `PageHeader` both use for `#avatar`.
242
+
243
+ It used to sit inside `__title`, copied from the coaching index prototype,
244
+ which hand-rolled it into a span in the title because it had no slot to use.
245
+ That indented the title and left the subtitle at the header's own edge, 46px
246
+ adrift from the heading it belongs to — three edges where there should be two.
247
+ Here the whole of `__text` (title, badge, subtitle) indents past it together.
248
+
249
+ Its own element, NOT the `#icon` slot: that slot is width-gated and a control
250
+ must not vanish on a narrow header (#372). `__header` is already `items-start`,
251
+ so it lands on the title's line rather than centring against the block. #407.
252
+ -->
253
+ <div v-if="rendersTrigger()"
254
+ class="rsui-section-header__trigger"
255
+ >
256
+ <DisclosureTrigger sm />
257
+ </div>
258
+
225
259
  <!-- Icon slot, optional — hidden on narrow sections -->
226
260
  <div class="rsui-section-header__icon"
227
261
  v-if="$slots.icon && responsiveWidth.specific"
@@ -237,24 +271,8 @@ const titleTag = computed(() => HEADING_LEVELS.includes(String(props.headingLeve
237
271
  <!-- Title slot, default slot -->
238
272
  <div :class="[
239
273
  'rsui-section-header__title',
240
- { 'rsui-section-header__title--stacked': !responsiveWidth.specific && !inlineBadge },
241
- // --stacked gives the title text `basis-full`, which takes the whole row and
242
- // strands the trigger on the line above it. Same class of defect CardHeader
243
- // had, and it carries the same modifier name.
244
- { 'rsui-section-header__title--with-trigger': rendersTrigger() },
274
+ { 'rsui-section-header__title--stacked': !responsiveWidth.specific && stackBadge },
245
275
  ]">
246
- <!--
247
- First child of the TITLE row, not a sibling of the whole text block
248
- the way `#icon` is: `__icon` centres against title AND subtitle
249
- together, so with a subtitle present the chevron drifts off the line
250
- it belongs to. `sm` matches the ACTION SIZE guidance in
251
- section_header.css.
252
- -->
253
- <div v-if="rendersTrigger()"
254
- class="rsui-section-header__trigger"
255
- >
256
- <DisclosureTrigger sm />
257
- </div>
258
276
  <component :is="titleTag"
259
277
  :class="[
260
278
  'rsui-section-header__title-text',